diff --git a/.github/docker/wolfsentry-check-all/Dockerfile b/.github/docker/wolfsentry-check-all/Dockerfile new file mode 100644 index 00000000..fdce8d16 --- /dev/null +++ b/.github/docker/wolfsentry-check-all/Dockerfile @@ -0,0 +1,63 @@ +FROM ubuntu:26.04@sha256:157dc3d6e4a3e4f2c68d12df9df9ed6e808b59af6d4c3757b432b4f519916725 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + autoconf \ + automake \ + ca-certificates \ + clang \ + clang-tidy \ + cppcheck \ + curl \ + doxygen \ + dwarves \ + gcc \ + gcc-15 \ + gcc-15-multilib \ + gcc-arm-none-eabi \ + gcc-multilib \ + git \ + graphviz \ + libnewlib-arm-none-eabi \ + libclang-rt-21-dev \ + libnotify-dev \ + libtool \ + llvm \ + make \ + patch \ + qemu-user \ + texlive-fonts-recommended \ + texlive-latex-base \ + texlive-latex-extra \ + texlive-latex-recommended \ + texlive-plain-generic \ + util-linux \ + valgrind \ + xz-utils \ + zip \ + && rm -rf /var/lib/apt/lists/* + +ARG ARMEB_TOOLCHAIN=armebv7-eabihf--glibc--stable-2026.08-1 +ARG ARMEB_TOOLCHAIN_SHA256=4d93a7b26c0909ea7574e98246522cf6b77491d43a7ca7aa08ae1c29ecf07b0d + +# Buildroot's compiler wrapper finds its real binary from argv[0], so expose the +# armeb-linux-gnueabihf names as exec shims, and put the sanitizer runtimes in the sysroot qemu loads from. +RUN curl -fsSL --retry 5 --retry-all-errors -o /tmp/armeb.tar.xz \ + "https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/${ARMEB_TOOLCHAIN}.tar.xz" \ + && echo "${ARMEB_TOOLCHAIN_SHA256} /tmp/armeb.tar.xz" | sha256sum -c - \ + && tar -xJf /tmp/armeb.tar.xz -C /opt \ + && rm /tmp/armeb.tar.xz \ + && tc="/opt/${ARMEB_TOOLCHAIN}" \ + && target="${tc}/armeb-buildroot-linux-gnueabihf" \ + && for f in "${tc}"/bin/armeb-buildroot-linux-gnueabihf-*; do \ + case "$f" in *.br_real) continue ;; esac; \ + shim="/usr/local/bin/$(basename "$f" | sed 's/^armeb-buildroot-/armeb-/')"; \ + printf '#!/bin/sh\nexec %s "$@"\n' "$f" > "$shim"; \ + chmod +x "$shim"; \ + done \ + && cp -a "${target}"/lib/libasan.so* "${target}"/lib/libubsan.so* "${target}"/lib/libatomic.so* "${target}/sysroot/lib/" \ + && ln -s "${target}/sysroot" /usr/armeb-linux-gnueabihf diff --git a/.github/docker/wolfsentry-ci/Dockerfile b/.github/docker/wolfsentry-ci/Dockerfile index d864936d..91643186 100644 --- a/.github/docker/wolfsentry-ci/Dockerfile +++ b/.github/docker/wolfsentry-ci/Dockerfile @@ -1,5 +1,7 @@ FROM ghcr.io/wolfssl/wolfcose-ci@sha256:c073c7afb4cd3f296b8c1a305db2f718530b7a8aca899482629aee3a5cf8bfbc +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries + RUN apt-get update \ && apt-get install -y --no-install-recommends \ bubblewrap \ diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml new file mode 100644 index 00000000..e362d20e --- /dev/null +++ b/.github/workflows/check-all.yml @@ -0,0 +1,90 @@ +name: make check-all + +run-name: ${{ inputs.nightly_run_id || github.workflow }} + +on: + push: + branches: [ 'master', 'release/**' ] + pull_request: + branches: [ '**' ] + workflow_dispatch: + inputs: + nightly_run_id: + description: Nightly coordinator run ID + required: false + type: string + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash -e {0} + +jobs: + check-all: + name: make check-all + runs-on: ubuntu-latest + timeout-minutes: 240 + env: + # built from .github/docker/wolfsentry-check-all by publish-ci-image.yml + CHECK_ALL_IMAGE: ghcr.io/wolfssl/wolfsentry-check-all@sha256:99be2efa32df4ea09a87706286b914e949e4b636787759dd4ae806f4b1aaae84 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + path: wolfsentry + fetch-depth: 0 + - uses: actions/checkout@v4 + with: + repository: FreeRTOS/FreeRTOS-Kernel + ref: dbf70559b27d39c1fdb68dfb9a32140b6a6777a0 + path: third/FreeRTOS/FreeRTOS/Source + - uses: actions/checkout@v4 + with: + repository: lwip-tcpip/lwip + ref: 3fe8d2fc43a9b69f7ed28c63d44a7744f9c0def9 + path: third/lwip + - uses: actions/checkout@v4 + with: + repository: json-schema-org/JSON-Schema-Test-Suite + ref: 245a45abc90190c0bff04d97e6d2b541b4bb9f12 + path: JSON-Schema-Test-Suite + - uses: actions/checkout@v4 + with: + repository: wolfSSL/wolfssl + ref: master + path: wolfssl + - name: Apply the lwIP packet filter patch + run: patch --directory third/lwip --strip 1 < wolfsentry/lwip/LWIP_PACKET_FILTER_API.patch + # stand-in until the maintainer's local scripts/analyzer-config.sh is available; cppcheck needs the file to exist + - name: Provide scripts/analyzer-config.sh + run: touch wolfsentry/scripts/analyzer-config.sh + - name: Lower mmap randomization for ThreadSanitizer + run: sudo sysctl -w vm.mmap_rnd_bits=28 + - name: Pull the check-all image + run: | + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker pull "$CHECK_ALL_IMAGE" + # make pre-push-check runs scripts/pre-push.sh, which runs make check-all on this commit. + # gcc 14 arm-none-eabi needs larger FreeRTOS image limits; the JSON corpus sits on tmpfs because + # qemu-user cannot pass ext4's 64-bit directory cookies to the 32-bit armeb tests. + - name: make pre-push-check + run: | + docker run --rm \ + --volume "$GITHUB_WORKSPACE:/workspace" \ + --workdir /workspace/wolfsentry \ + --mount type=tmpfs,destination=/workspace/wolfsentry/tests/JSON-Schema-Test-Suite \ + --env TMPDIR=/tmp \ + --env CLANG_PATH=/usr/bin \ + --env NOTIFICATION_SANDBOX= \ + --env FREERTOS_ARM32_MAX_IMAGE_SIZE=270000 \ + --env FREERTOS_ARM32_MAX_SECTIONS_SIZE=145000 \ + --env FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE=145000 \ + "$CHECK_ALL_IMAGE" \ + bash -ec 'cp -a /workspace/JSON-Schema-Test-Suite/. tests/JSON-Schema-Test-Suite/; git config --global --add safe.directory "*"; make pre-push-check' diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5020f7f6..19bc7606 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -36,7 +36,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and start the lwIP network - run: docker compose up --build --detach + run: | + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker compose up --build --detach - name: Exercise allowed and rejected traffic run: | for attempt in $(seq 1 30); do @@ -94,7 +96,8 @@ jobs: path: wolfip - name: Build the wolfSentry CI image run: | - docker build \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker build \ --file wolfsentry/.github/docker/wolfsentry-ci/Dockerfile \ --tag wolfsentry-ci:local wolfsentry - name: Build and run the wolfIP example @@ -146,7 +149,8 @@ jobs: path: wolfssl - name: Build the wolfSentry CI image run: | - docker build \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker build \ --file wolfsentry/.github/docker/wolfsentry-ci/Dockerfile \ --tag wolfsentry-ci:local wolfsentry - name: Build and exercise the notification demo @@ -195,7 +199,8 @@ jobs: path: lwip - name: Check out lwIP contrib run: | - git clone --branch STABLE-2_1_0_RELEASE --depth 1 \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry git clone --branch STABLE-2_1_0_RELEASE --depth 1 \ https://git.savannah.nongnu.org/git/lwip/lwip-contrib.git \ lwip/contrib test "$(git -C lwip/contrib rev-parse HEAD)" = \ diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9ef5f4f1..a2bff711 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -33,6 +33,7 @@ jobs: const POLL_TIMEOUT_MS = 120 * 60 * 1000; const dispatchToken = `nightly-${context.runId}-${context.runAttempt}`; const workflows = [ + 'check-all.yml', 'codeql.yml', 'coverage.yml', 'examples.yml', diff --git a/.github/workflows/publish-ci-image.yml b/.github/workflows/publish-ci-image.yml index 0ea0986c..85f8fb30 100644 --- a/.github/workflows/publish-ci-image.yml +++ b/.github/workflows/publish-ci-image.yml @@ -5,6 +5,7 @@ on: branches: [ '**' ] paths: - '.github/docker/wolfsentry-ci/**' + - '.github/docker/wolfsentry-check-all/**' - '.github/workflows/publish-ci-image.yml' workflow_dispatch: @@ -80,3 +81,68 @@ jobs: scan-build --help > /dev/null /usr/bin/python3 -c 'import dbus, gi' make -j2 test + + publish-check-all: + name: Publish check-all image + if: github.repository == 'wolfSSL/wolfsentry' + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + packages: write + outputs: + digest: ${{ steps.build.outputs.digest }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push image + id: build + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + file: .github/docker/wolfsentry-check-all/Dockerfile + platforms: linux/amd64 + pull: true + push: true + tags: | + ghcr.io/wolfssl/wolfsentry-check-all:sha-${{ github.sha }} + ghcr.io/wolfssl/wolfsentry-check-all:${{ github.ref == 'refs/heads/master' && 'latest' || 'edge' }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + cache-from: type=registry,ref=ghcr.io/wolfssl/wolfsentry-check-all:latest + cache-to: type=inline + + smoke-check-all: + name: Test published check-all image + needs: publish-check-all + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: read + container: + image: ghcr.io/wolfssl/wolfsentry-check-all@${{ needs.publish-check-all.outputs.digest }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + defaults: + run: + shell: bash -e {0} + steps: + - name: Exercise image + run: | + echo "digest: ${{ needs.publish-check-all.outputs.digest }}" + gcc-15 --version + clang --version + clang-tidy --version + cppcheck --version + arm-none-eabi-gcc --version + armeb-linux-gnueabihf-gcc --version + qemu-armeb --version + test -e /usr/armeb-linux-gnueabihf/lib/libasan.so.8 diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 3d9f8787..ed46b96f 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -37,9 +37,10 @@ jobs: - name: Build matrix id: versions run: | - LATEST=$(git ls-remote --tags --refs \ - https://github.com/wolfSSL/wolfssl.git 'v*-stable' \ - | awk -F/ '{print $NF}' | sort -V | tail -n 1) + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + TAGS=$(retry git ls-remote --tags --refs \ + https://github.com/wolfSSL/wolfssl.git 'v*-stable') + LATEST=$(awk -F/ '{print $NF}' <<<"$TAGS" | sort -V | tail -n 1) test -n "$LATEST" if [[ ! "$LATEST" =~ ^v[0-9]+(\.[0-9]+)+-stable$ ]]; then echo "Unexpected wolfSSL stable tag: $LATEST" >&2 @@ -83,7 +84,8 @@ jobs: env: WOLFSSL_REF: ${{ matrix.ref }} run: | - git clone --depth 1 --branch "$WOLFSSL_REF" \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry git clone --depth 1 --branch "$WOLFSSL_REF" \ https://github.com/wolfSSL/wolfssl.git "$HOME/wolfssl" cd "$HOME/wolfssl" ./autogen.sh diff --git a/Makefile.analyzers b/Makefile.analyzers index fb91cb1c..ed895101 100644 --- a/Makefile.analyzers +++ b/Makefile.analyzers @@ -432,7 +432,7 @@ minimal-threaded-build-test: @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-minimal-threaded-builds" clean @echo "passed: minimal threaded build test." -LIBRARY_DEPENDENCY_SUPPRESSIONS_BASE := U wolfsentry_|U json_|U (inet_ntop|inet_pton|memchr|memcmp|memcpy|__memcpy_chk|memmove|memset|__popcountdi2|snprintf|__snprintf_chk|strchr|strcmp|strlen|strncasecmp|strncmp|strtod|strtol|strtoll|strtoul|strtoull)$$ +LIBRARY_DEPENDENCY_SUPPRESSIONS_BASE := U wolfsentry_|U json_|U (inet_ntop|inet_pton|memchr|memcmp|memcpy|__memcpy_chk|memmove|memset|__popcountdi2|snprintf|__snprintf_chk|strchr|strcmp|strlen|strncasecmp|strncmp|strtod|strtol|strtoll|strtoul|strtoull|__isoc23_strtol|__isoc23_strtoll|__isoc23_strtoul|__isoc23_strtoull)$$ .PHONY: library-dependency-singlethreaded-build-test library-dependency-singlethreaded-build-test: @@ -566,6 +566,9 @@ else LWIP_TOP_FOR_TEST := $(SRC_TOP)/../third/lwip endif +FREERTOS_ARM32_MAX_IMAGE_SIZE ?= 240000 +FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE ?= 110000 + .PHONY: freertos-arm32-thumb-build-test freertos-arm32-thumb-build-test: @[ -d $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source/. ] || ( echo '$@: $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source not found.' >&2; exit 1) @@ -574,16 +577,18 @@ freertos-arm32-thumb-build-test: @rm -rf "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" # first build without FUNCTION_SECTIONS=1 to detect any bloat or unresolved symbols anywhere in the library. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 230000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_MAX_IMAGE_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-thumb-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7' UNITTEST_LIST=test_lwip clean # now rebuild with FUNCTION_SECTIONS=1, to test expected text size on a parsimonious link. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 FUNCTION_SECTIONS=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 110000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-thumb-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7' UNITTEST_LIST=test_lwip clean @echo "passed: freertos-arm32-thumb-build test." +FREERTOS_ARM32_MAX_SECTIONS_SIZE ?= 100000 + .PHONY: freertos-arm32-build-test freertos-arm32-build-test: @[ -d $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source/. ] || ( echo '$@: $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source not found.' >&2; exit 1) @@ -592,12 +597,12 @@ freertos-arm32-build-test: @rm -rf "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" # first build without FUNCTION_SECTIONS=1 to detect any bloat or unresolved symbols anywhere in the library. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 230000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_MAX_IMAGE_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3' UNITTEST_LIST=test_lwip clean # now rebuild with FUNCTION_SECTIONS=1, to test expected text size on a parsimonious link. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 FUNCTION_SECTIONS=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 100000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_MAX_SECTIONS_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3' UNITTEST_LIST=test_lwip clean @echo "passed: freertos-arm32-build test." diff --git a/doc/json_configuration.md b/doc/json_configuration.md index 9aa1bc86..dcb00239 100644 --- a/doc/json_configuration.md +++ b/doc/json_configuration.md @@ -239,7 +239,7 @@ Each event is composed of the following elements, all of which are optional exce * `label` -- The name by which the event is identified. See the definition of `label` in the ABNF grammar below for permissible values. -* `priority` -- The priority of routes that have this event as their `parent-event` (see `routes` below). Lower number means higher priority. +* `priority` -- The priority of routes that have this event as their `parent-event` (see `routes` below). Lower number means higher priority. If omitted, the priority is 0, the highest priority. * `config` -- The configuration to associate with routes with this `parent-event`, as above for `config-update`. diff --git a/examples/Linux-LWIP/Dockerfile b/examples/Linux-LWIP/Dockerfile index 372da55a..4bc61b62 100644 --- a/examples/Linux-LWIP/Dockerfile +++ b/examples/Linux-LWIP/Dockerfile @@ -1,5 +1,7 @@ FROM debian:12 AS builder +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries + RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential ca-certificates cmake git libpcap-dev netbase patch \ && rm -rf /var/lib/apt/lists/* @@ -8,9 +10,10 @@ WORKDIR /src COPY . wolfsentry -RUN git clone --depth 1 --branch STABLE-2_1_3_RELEASE \ +RUN retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } \ + && retry git clone --depth 1 --branch STABLE-2_1_3_RELEASE \ https://github.com/lwip-tcpip/lwip.git \ - && git clone --depth 1 --branch STABLE-2_1_0_RELEASE \ + && retry git clone --depth 1 --branch STABLE-2_1_0_RELEASE \ https://git.savannah.nongnu.org/git/lwip/lwip-contrib.git \ lwip/contrib \ && cd lwip \ diff --git a/examples/Linux-LWIP/echo.c b/examples/Linux-LWIP/echo.c index 95e88816..288ed165 100644 --- a/examples/Linux-LWIP/echo.c +++ b/examples/Linux-LWIP/echo.c @@ -95,16 +95,32 @@ static err_t echo_msgaccept(void *arg, struct tcp_pcb *pcb, err_t err) int echo_init(void) { struct tcp_pcb *pcb; + struct tcp_pcb *listen_pcb; + int r; /* Create lwIP TCP instance */ pcb = tcp_new(); LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: pcb: %x\n", pcb)); + if (pcb == NULL) + return -1; /* Bind port 11111 */ - int r = tcp_bind(pcb, IP_ADDR_ANY, 11111); + r = tcp_bind(pcb, IP_ADDR_ANY, 11111); LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: tcp_bind: %d\n", r)); - /* Enable listening */ - pcb = tcp_listen(pcb); - LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: listen-pcb: %x\n", pcb)); + if (r != ERR_OK) { + if (tcp_close(pcb) != ERR_OK) + tcp_abort(pcb); + return -1; + } + /* Enable listening. tcp_listen() frees the original pcb only on success; + * on failure it returns NULL and leaves the original allocated. */ + listen_pcb = tcp_listen(pcb); + LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: listen-pcb: %x\n", listen_pcb)); + if (listen_pcb == NULL) { + if (tcp_close(pcb) != ERR_OK) + tcp_abort(pcb); + return -1; + } + pcb = listen_pcb; /* Set accept connection callback */ tcp_accept(pcb, echo_msgaccept); diff --git a/examples/Linux-LWIP/ping.c b/examples/Linux-LWIP/ping.c index ed497eba..ad03fea5 100644 --- a/examples/Linux-LWIP/ping.c +++ b/examples/Linux-LWIP/ping.c @@ -40,7 +40,8 @@ int ping_init(void) struct raw_pcb *ping_pcb; /* Create a new listener instance for ICMP messages */ ping_pcb = raw_new(IP_PROTO_ICMP); - LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL); + if (ping_pcb == NULL) + return -1; /* Callback to ping_recv in ICMP message received */ raw_recv(ping_pcb, ping_recv, NULL); diff --git a/examples/Linux-LWIP/sentry.c b/examples/Linux-LWIP/sentry.c index acc24c7d..82489783 100644 --- a/examples/Linux-LWIP/sentry.c +++ b/examples/Linux-LWIP/sentry.c @@ -45,113 +45,143 @@ static wolfsentry_errcode_t test_action( int sentry_init() { wolfsentry_errcode_t ret; + wolfsentry_errcode_t fini_ret; wolfsentry_ent_id_t id; - + char buf[512], err_buf[512]; + struct wolfsentry_json_process_state *jps = NULL; + FILE *f = NULL; + size_t n; struct wolfsentry_eventconfig config = { .route_private_data_size = 32, .route_private_data_alignment = 16 }; + ret = wolfsentry_init(wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(NULL /* hpi */, NULL /* thread */), &config, &wolfsentry); if (ret < 0) { fprintf(stderr, "wolfsentry_init() returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); fprintf(stderr, "unable to initialize wolfSentry"); + return ret; } - /* Insert the possible actions into wolfSentry */ - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-insert", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-delete", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "notify-on-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect2", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - - char buf[512], err_buf[512]; - struct wolfsentry_json_process_state *jps; + /* Insert the possible actions into wolfSentry. On any failure fall through + * to the cleanup path at the bottom so the context is not leaked. */ + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-delete", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "notify-on-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-connect", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-connect2", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); /* Open the config file */ - FILE *f = fopen(wolfsentry_config_path, "r"); - - if (f == NULL) { - fprintf(stderr, "fopen(%s): %s\n",wolfsentry_config_path,strerror(errno)); - fprintf(stderr, "unable to open wolfSentry config file"); + if (ret >= 0) { + f = fopen(wolfsentry_config_path, "r"); + if (f == NULL) { + fprintf(stderr, "fopen(%s): %s\n",wolfsentry_config_path,strerror(errno)); + fprintf(stderr, "unable to open wolfSentry config file"); + ret = -1; + } } /* Initalize the wolfSentry JSON parser */ - if ((ret = wolfsentry_config_json_init( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, - &jps)) < 0) { - fprintf(stderr, "wolfsentry_config_json_init() returned " - WOLFSENTRY_ERROR_FMT "\n", - WOLFSENTRY_ERROR_FMT_ARGS(ret)); - fprintf(stderr, "error while initializing wolfSentry config parser"); + if (ret >= 0) { + ret = wolfsentry_config_json_init( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + &jps); + if (ret < 0) { + fprintf(stderr, "wolfsentry_config_json_init() returned " + WOLFSENTRY_ERROR_FMT "\n", + WOLFSENTRY_ERROR_FMT_ARGS(ret)); + fprintf(stderr, "error while initializing wolfSentry config parser"); + } } /* wolfSentry uses a streaming reader/parser for the config file */ - for (;;) { - /* Read some data from the config file */ - size_t n = fread(buf, 1, sizeof buf, f); - if ((n < sizeof buf) && ferror(f)) { - fprintf(stderr, "fread(%s): %s\n",wolfsentry_config_path, strerror(errno)); - fprintf(stderr, "error while reading wolfSentry config file"); + if (ret >= 0) { + for (;;) { + /* Read some data from the config file */ + n = fread(buf, 1, sizeof buf, f); + if ((n < sizeof buf) && ferror(f)) { + fprintf(stderr, "fread(%s): %s\n",wolfsentry_config_path, strerror(errno)); + fprintf(stderr, "error while reading wolfSentry config file"); + ret = -1; + break; + } + + /* Send the read data into the JSON parser */ + ret = wolfsentry_config_json_feed(jps, buf, n, err_buf, sizeof err_buf); + if (ret < 0) { + fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); + fprintf(stderr, "error while loading wolfSentry config file"); + break; + } + if ((n < sizeof buf) && feof(f)) + break; } + } - /* Send the read data into the JSON parser */ - ret = wolfsentry_config_json_feed(jps, buf, n, err_buf, sizeof err_buf); - if (ret < 0) { + /* Clean up the JSON parser. This also commits the parsed config, so its + * result matters on the success path. */ + if (jps != NULL) { + fini_ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf); + if (fini_ret < 0) { fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); fprintf(stderr, "error while loading wolfSentry config file"); + if (ret >= 0) + ret = fini_ret; } - if ((n < sizeof buf) && feof(f)) - break; } - fclose(f); - /* Clean up the JSON parser */ - if ((ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf)) < 0) { - fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); - fprintf(stderr, "error while loading wolfSentry config file"); + if (f != NULL) + fclose(f); + + /* Any failure after wolfsentry_init() must release the context. wolfSentry + * success codes are nonnegative but not necessarily zero, so normalize. */ + if (ret < 0) { + if (wolfsentry != NULL) + (void)wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(&wolfsentry, NULL)); + return ret; } return 0; @@ -218,6 +248,11 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port fprintf(stderr, "TCP Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + /* Fail closed: any dispatch error denies the connection */ + if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) < 0) { + return -1; + } + /* Check the result, if it contains "reject" then notify the caller */ if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { @@ -261,6 +296,10 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) &action_results); fprintf(stderr, "PING Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + /* Fail closed: any dispatch error denies the packet */ + if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) < 0) { + return -1; + } if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { return -1; @@ -302,6 +341,10 @@ int sentry_action_mac(struct eth_addr *addr) fprintf(stderr, "MAC Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + /* Fail closed: any dispatch error denies the frame */ + if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) < 0) { + return -1; + } if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { return -1; diff --git a/examples/STM32-LWIP/README.md b/examples/STM32-LWIP/README.md index 430cc6e7..f2c24b61 100644 --- a/examples/STM32-LWIP/README.md +++ b/examples/STM32-LWIP/README.md @@ -68,7 +68,10 @@ In `StartDefaultTask()`: ```c /* USER CODE BEGIN 5 */ printf("Sentry init\n"); - sentry_init(); + if (sentry_init() != 0) { + printf("Sentry init failed, all traffic will be denied\n"); + Error_Handler(); + } printf("Echo init\n"); echo_init(); printf("Ping init\n"); diff --git a/examples/STM32-LWIP/Src/sentry.c b/examples/STM32-LWIP/Src/sentry.c index 69b7f6ac..035f60a0 100644 --- a/examples/STM32-LWIP/Src/sentry.c +++ b/examples/STM32-LWIP/Src/sentry.c @@ -135,25 +135,28 @@ static wolfsentry_errcode_t test_action( int sentry_init(void) { wolfsentry_errcode_t ret; + wolfsentry_errcode_t fini_ret; wolfsentry_ent_id_t id; + struct wolfsentry_context *ctx = NULL; struct wolfsentry_eventconfig config = { 0 }; char err_buf[512]; - struct wolfsentry_json_process_state *jps; + struct wolfsentry_json_process_state *jps = NULL; config.route_private_data_size = 32; config.route_private_data_alignment = 16; ret = wolfsentry_init(wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(NULL /* hpi */, NULL /* thread */), &config, - &wolfsentry); + &ctx); if (ret < 0) { printf("unable to initialize wolfSentry, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + return ret; } /* Insert the possible actions into wolfSentry */ - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), "handle-insert", WOLFSENTRY_LENGTH_NULL_TERMINATED, WOLFSENTRY_ACTION_FLAG_NONE, @@ -161,83 +164,115 @@ int sentry_init(void) NULL, &id); - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-delete", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "notify-on-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect2", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-delete", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "notify-on-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-connect", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-connect2", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret < 0) + { + printf("unable to insert wolfSentry actions, " WOLFSENTRY_ERROR_FMT "\n", + WOLFSENTRY_ERROR_FMT_ARGS(ret)); + } /* Initalize the wolfSentry JSON parser */ - if ((ret = wolfsentry_config_json_init( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, - &jps)) < 0) + if (ret >= 0) { - printf("error while initializing wolfSentry config parser, " - WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + ret = wolfsentry_config_json_init( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + &jps); + if (ret < 0) + { + printf("error while initializing wolfSentry config parser, " + WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + } } /* wolfSentry uses a streaming reader/parser for the config file */ /* Send the read data into the JSON parser */ - ret = wolfsentry_config_json_feed(jps, - (const unsigned char *)wolfsentry_config_data, - strlen(wolfsentry_config_data), err_buf, sizeof err_buf); - - if (ret < 0) - { - printf("error while loading wolfSentry config data, " - WOLFSENTRY_ERROR_FMT ", msg: %s\n", - WOLFSENTRY_ERROR_FMT_ARGS(ret), err_buf); - } - - /* Clean up the JSON parser */ - if ((ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf)) < 0) + if (ret >= 0) { - printf("error while loading wolfSentry config data, " - WOLFSENTRY_ERROR_FMT ", msg: %s\n", - WOLFSENTRY_ERROR_FMT_ARGS(ret), err_buf); + ret = wolfsentry_config_json_feed(jps, + (const unsigned char *)wolfsentry_config_data, + strlen(wolfsentry_config_data), err_buf, sizeof err_buf); + if (ret < 0) + { + printf("error while loading wolfSentry config data, " + WOLFSENTRY_ERROR_FMT ", msg: %s\n", + WOLFSENTRY_ERROR_FMT_ARGS(ret), err_buf); + } } + /* Clean up the JSON parser. This commits the parsed config. */ + if (jps != NULL) + { + fini_ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf); + if (fini_ret < 0) + { + printf("error while loading wolfSentry config data, " + WOLFSENTRY_ERROR_FMT ", msg: %s\n", + WOLFSENTRY_ERROR_FMT_ARGS(fini_ret), err_buf); + if (ret >= 0) + ret = fini_ret; + } + } + + if (ret < 0) + { + (void)wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(&ctx, NULL)); + return ret; + } + + /* Publish the context only once the policy is committed, so a concurrent + * packet filter never dispatches against an empty route table. */ + WOLFSENTRY_ATOMIC_STORE(wolfsentry, ctx); + return 0; } @@ -247,7 +282,7 @@ int sentry_init(void) int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port, uint16_t remote_port, sentry_action_type action) { wolfsentry_errcode_t ret; - wolfsentry_action_res_t action_results; + wolfsentry_action_res_t action_results = WOLFSENTRY_ACTION_RES_NONE; /* Note that sa.addr is 0 bytes, addr_buf essentially enlarges this to the correct size */ struct { struct wolfsentry_sockaddr sa; @@ -255,6 +290,11 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port } remote, local; u32_t remoteip = remote_ip->addr; u32_t localip = local_ip->addr; + struct wolfsentry_context *ctx = WOLFSENTRY_ATOMIC_LOAD(wolfsentry); + + /* Fail closed until initialization has published a configured context */ + if (ctx == NULL) + return -1; /* Connect will increment the connection count in wolfSentry, disconnect * will decrement it */ @@ -289,7 +329,7 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port /* Send the details of this to wolfSentry and get the result */ ret = wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, @@ -304,11 +344,14 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port WOLFSENTRY_ERROR_FMT_ARGS(ret)); fflush(stdout); + /* Fail closed: a dispatch error denies the connection */ + if (ret < 0) { + return -1; + } + /* Check the result, if it contains "reject" then notify the caller */ - if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { - return -1; - } + if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { + return -1; } return 0; @@ -324,6 +367,11 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) struct wolfsentry_sockaddr sa; byte addr_buf[4]; } remote; + struct wolfsentry_context *ctx = WOLFSENTRY_ATOMIC_LOAD(wolfsentry); + + /* Fail closed until initialization has published a configured context */ + if (ctx == NULL) + return -1; /* ICMP protocol check. The ICMP packet type is the port number for this * check */ @@ -335,7 +383,7 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) memcpy(remote.sa.addr, &addr->addr, 4); ret = wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), &remote.sa, &remote.sa, /* Reuse for now */ WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, @@ -348,10 +396,14 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) printf("PING Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); fflush(stdout); - if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { - return -1; - } + + /* Fail closed: a dispatch error denies the packet */ + if (ret < 0) { + return -1; + } + + if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { + return -1; } return 0; @@ -367,6 +419,11 @@ int sentry_action_mac(struct eth_addr *addr) struct wolfsentry_sockaddr sa; byte addr_buf[6]; } remote; + struct wolfsentry_context *ctx = WOLFSENTRY_ATOMIC_LOAD(wolfsentry); + + /* Fail closed until initialization has published a configured context */ + if (ctx == NULL) + return -1; /* We only really care about the data and length, the family is AF_LINK */ remote.sa.sa_family = WOLFSENTRY_AF_LINK; @@ -376,7 +433,7 @@ int sentry_action_mac(struct eth_addr *addr) memcpy(remote.sa.addr, &addr->addr, 6); ret = wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), &remote.sa, &remote.sa, /* Reuse for now */ WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, @@ -390,10 +447,14 @@ int sentry_action_mac(struct eth_addr *addr) printf("MAC Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); fflush(stdout); - if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { - return -1; - } + + /* Fail closed: a dispatch error denies the frame */ + if (ret < 0) { + return -1; + } + + if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { + return -1; } return 0; diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index c90d2812..2c3a3dc2 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -315,6 +315,10 @@ wolfsentry_errcode_t circlog_enqueue_one(size_t msg_len, char **msg_buf) { if (new_ent_size > circlog_size) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + /* reset a fully drained queue so a wrap cannot strand a stale head. */ + if (circlog_head == circlog_tail) + circlog_head = circlog_tail = 0; + if (circlog_tail + new_ent_size > circlog_size) { /* if the new ent will wrap, old ents need to be cleared out to the end, and a skip ent inserted. */ if (circlog_tail + circlog_ent_size_from_msg_len(0) < circlog_size) { @@ -342,7 +346,8 @@ wolfsentry_errcode_t circlog_enqueue_one(size_t msg_len, char **msg_buf) { * the =, circlog_tail could be incremented below to equal circlog_head, * which is defined to be an empty queue. */ - while ((circlog_head > new_msg_start) && + while ((circlog_nents > 0) && + (circlog_head >= new_msg_start) && (circlog_head <= new_msg_start + new_ent_size)) { ret = circlog_dequeue_one(NULL); @@ -350,6 +355,10 @@ wolfsentry_errcode_t circlog_enqueue_one(size_t msg_len, char **msg_buf) { return ret; } + /* if eviction drained the queue, the new entry alone defines the head. */ + if (circlog_nents == 0) + circlog_head = new_msg_start; + new_ent = (struct circlog_message *)(void *)(circlog + new_msg_start); ret = wolfsentry_time_now_plus_delta(global_wolfsentry, 0 /* td */, &new_ent->when); new_ent->len = (uint16_t)msg_len; @@ -417,15 +426,37 @@ struct log_server_data { int error_code; }; +#if defined(DEBUG_TLS) && defined(OPENSSL_EXTRA) +/* certificate DN strings are peer-controlled; drop control bytes before logging. */ +static const char *sanitize_log_str(const char *in, char *out, size_t outsz) { + size_t i; + unsigned char c; + + if (in == NULL) + return "(null)"; + if (outsz == 0) + return out; + for (i = 0; (i + 1 < outsz) && (in[i] != '\0'); ++i) { + c = (unsigned char)in[i]; + if ((c < 0x20) || (c == 0x7f)) + out[i] = '?'; + else + out[i] = (char)c; + } + out[i] = '\0'; + return out; +} +#endif static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { struct log_server_data *app_data = NULL; - const char* issuerName = NULL; + char* issuerName = NULL; unsigned long issuerHash = 0; #if defined(DEBUG_TLS) && defined(OPENSSL_EXTRA) const char *subjectCN; - const char* subjectName; + char* subjectName = NULL; unsigned long subjectHash = 0; + char subjectCN_buf[256], subjectName_buf[256], issuerName_buf[256]; #endif struct ca_cert *ca_certs_i; @@ -473,6 +504,10 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { issuerHash = wolfSSL_X509_issuer_name_hash(store->current_cert); issuerName = wolfSSL_X509_NAME_oneline( wolfSSL_X509_get_issuer_name(store->current_cert), 0, 0); + if (issuerName == NULL) { + fprintf(stderr, "ERROR: %s L%d null issuer name.\n", __FILE__,__LINE__); + return WOLFSSL_FAILURE; + } #ifdef DEBUG_TLS subjectCN = wolfSSL_X509_get_subjectCN(store->current_cert); @@ -480,8 +515,11 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { subjectName = wolfSSL_X509_NAME_oneline( wolfSSL_X509_get_subject_name(store->current_cert), 0, 0); fprintf(stderr, "CN=%s subjectName->name=\"%s\" subject_hash=%lu\n", - subjectCN, subjectName, subjectHash); - fprintf(stderr, "issuerName->name=\"%s\" issuer_hash=%lu\n", issuerName, issuerHash); + sanitize_log_str(subjectCN, subjectCN_buf, sizeof subjectCN_buf), + sanitize_log_str(subjectName, subjectName_buf, sizeof subjectName_buf), + subjectHash); + fprintf(stderr, "issuerName->name=\"%s\" issuer_hash=%lu\n", + sanitize_log_str(issuerName, issuerName_buf, sizeof issuerName_buf), issuerHash); #endif #endif @@ -497,11 +535,23 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { } if (app_data->issuer_cert == NULL) { fprintf(stderr, "ERROR: %s L%d app_data->issuer_cert NULL.\n",__FILE__,__LINE__); +#ifdef OPENSSL_EXTRA + XFREE(issuerName, NULL, DYNAMIC_TYPE_OPENSSL); + #ifdef DEBUG_TLS + XFREE(subjectName, NULL, DYNAMIC_TYPE_OPENSSL); + #endif +#endif return WOLFSSL_FAILURE; } app_data->error_code = 0; +#ifdef OPENSSL_EXTRA + XFREE(issuerName, NULL, DYNAMIC_TYPE_OPENSSL); + #ifdef DEBUG_TLS + XFREE(subjectName, NULL, DYNAMIC_TYPE_OPENSSL); + #endif +#endif return WOLFSSL_SUCCESS; } @@ -1120,6 +1170,7 @@ static int http_write_header(WOLFSSL *ssl, char *http_buf, size_t http_buf_size, #ifndef NO_FILESYSTEM /* reads file size, allocates buffer, reads into buffer, returns buffer */ #include +#define LOAD_FILE_MAX_SZ (64 * 1024 * 1024) static int load_file(const char* fname, unsigned char** buf, size_t* bufLen) { int ret; @@ -1142,9 +1193,9 @@ static int load_file(const char* fname, unsigned char** buf, size_t* bufLen) } fseek(lFile, 0, SEEK_END); - fileSz = (int)ftell(lFile); + fileSz = ftell(lFile); rewind(lFile); - if (fileSz > 0) { + if ((fileSz > 0) && (fileSz <= (long int)LOAD_FILE_MAX_SZ)) { *bufLen = (size_t)fileSz; *buf = (unsigned char*)malloc(*bufLen); if (*buf == NULL) { @@ -1417,6 +1468,12 @@ int main(int argc, char **argv) { exit(1); } + if (circlog_size_uint64 > (uint64_t)SIZE_MAX) { + fprintf(stderr, "ERROR: \"circlog-size\" value %llu exceeds SIZE_MAX on this ABI\n", + (unsigned long long)circlog_size_uint64); + exit(1); + } + ret = circlog_init((size_t)circlog_size_uint64); if (ret < 0) { fprintf(stderr, "ERROR: circlog_init() failed: " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); @@ -1477,6 +1534,47 @@ int main(int argc, char **argv) { fprintf(stderr,"circlog_head=%zu circlog_tail=%zu\n",circlog_head,circlog_tail); } + + if (circlog != NULL) { + char *wrap_buf; + struct circlog_message *wrap_msg; + size_t wrap_filled, wrap_reachable; + static const char wrap_s[] = "wrap eviction regression probe entry."; + size_t wrap_ent = circlog_ent_size_from_msg_len(strlen(wrap_s)); + + if ((circlog_reset() >= 0) && ((wrap_ent * 2) <= circlog_size)) { + wrap_filled = 0; + while ((circlog_tail + wrap_ent) <= circlog_size) { + ret = circlog_enqueue_one(strlen(wrap_s), &wrap_buf); + if (ret < 0) { + fprintf(stderr, "ERROR: %s L%d circlog failed: " WOLFSENTRY_ERROR_FMT "\n", __FILE__, __LINE__, WOLFSENTRY_ERROR_FMT_ARGS(ret)); + exit(1); + } + memcpy(wrap_buf, wrap_s, strlen(wrap_s)); + ++wrap_filled; + } + + ret = circlog_enqueue_one(strlen(wrap_s), &wrap_buf); + if (ret < 0) { + fprintf(stderr, "ERROR: %s L%d circlog failed: " WOLFSENTRY_ERROR_FMT "\n", __FILE__, __LINE__, WOLFSENTRY_ERROR_FMT_ARGS(ret)); + exit(1); + } + memcpy(wrap_buf, wrap_s, strlen(wrap_s)); + + wrap_reachable = 0; + wrap_msg = NULL; + while (circlog_iterate(&wrap_msg) >= 0) + ++wrap_reachable; + + if (circlog_nents != wrap_reachable) { + fprintf(stderr, "ERROR: %s L%d circlog nents=%zu disagrees with reachable=%zu after wrap (filled=%zu).\n", + __FILE__, __LINE__, circlog_nents, wrap_reachable, wrap_filled); + exit(1); + } + + fprintf(stderr, "circlog wrap-eviction check OK: nents=%zu reachable=%zu\n", circlog_nents, wrap_reachable); + } + } #endif /* CIRCLOG_UNIT_TEST */ log_server_init(); @@ -1871,7 +1969,8 @@ int main(int argc, char **argv) { } #endif - if (! (wolfsentry_data->action_results & WOLFSENTRY_ACTION_RES_FALLTHROUGH)) { + if ((! (wolfsentry_data->action_results & WOLFSENTRY_ACTION_RES_FALLTHROUGH)) && + (wolfsentry_data->rule_route != NULL)) { wolfsentry_action_res_t action_results; if (transaction_successful) { ret = wolfsentry_route_event_dispatch_by_route(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(global_wolfsentry, global_thread), diff --git a/examples/notification-demo/log_server/sentry.c b/examples/notification-demo/log_server/sentry.c index e415539c..ff3a23b2 100644 --- a/examples/notification-demo/log_server/sentry.c +++ b/examples/notification-demo/log_server/sentry.c @@ -320,6 +320,39 @@ static int wolfSentry_NetworkFilterCallback( } +/* labels come from loaded config; escape them so they cannot break the JSON record. */ +static const char *json_escape_label(const char *in, char *out, size_t outsz) { + size_t i, o; + unsigned char c; + + if (in == NULL) + return ""; + if (outsz == 0) + return ""; + o = 0; + for (i = 0; in[i] != '\0'; ++i) { + c = (unsigned char)in[i]; + if ((c == '"') || (c == '\\')) { + if ((o + 2) >= outsz) + break; + out[o++] = '\\'; + out[o++] = (char)c; + } + else if ((c < 0x20) || (c == 0x7f)) { + if ((o + 1) >= outsz) + break; + out[o++] = '?'; + } + else { + if ((o + 1) >= outsz) + break; + out[o++] = (char)c; + } + } + out[o] = '\0'; + return out; +} + static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_action *action, @@ -353,6 +386,10 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( char msgbuf[1024], *msgbuf_ptr = msgbuf; int msgbuf_space_left = (int)sizeof msgbuf; int msgbuf_len; + char action_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; + char trigger_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; + char parent_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; + char family_name_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; (void)handler_arg; (void)route_table; @@ -451,12 +488,12 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( (size_t)msgbuf_space_left, "{ \"timestamp\" : \"%s\", \"action\" : \"%s\", \"trigger\" : %s%s%s, \"parent\" : %s%s%s, \"rule-id\" : " WOLFSENTRY_ENT_ID_FMT ", \"rule-hitcount\" : " WOLFSENTRY_HITCOUNT_FMT ", \"af\" : \"%s\", \"proto\" : %d, \"remote\" : { \"address\" : \"", timebuf, - wolfsentry_action_get_label(action), - trigger_event ? "\"" : "", trigger_event ? wolfsentry_event_get_label(trigger_event) : "null", trigger_event ? "\"" : "", - trigger_route_exports.parent_event_label ? "\"" : "", trigger_route_exports.parent_event_label ? trigger_route_exports.parent_event_label : "null", trigger_route_exports.parent_event_label ? "\"" : "", + json_escape_label(wolfsentry_action_get_label(action), action_label_buf, sizeof action_label_buf), + trigger_event ? "\"" : "", trigger_event ? json_escape_label(wolfsentry_event_get_label(trigger_event), trigger_label_buf, sizeof trigger_label_buf) : "null", trigger_event ? "\"" : "", + trigger_route_exports.parent_event_label ? "\"" : "", trigger_route_exports.parent_event_label ? json_escape_label(trigger_route_exports.parent_event_label, parent_label_buf, sizeof parent_label_buf) : "null", trigger_route_exports.parent_event_label ? "\"" : "", wolfsentry_get_object_id(rule_route), rule_route_exports.meta.hit_count, - family_name, + json_escape_label(family_name, family_name_buf, sizeof family_name_buf), trigger_route_exports.sa_proto); msgbuf_space_left -= msgbuf_len; @@ -551,10 +588,10 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( if ((res_string = wolfsentry_action_res_assoc_by_flag(*action_results, res_bit)) != NULL) { ++n_res_bits; if (n_res_bits > 1) { + if (msgbuf_space_left <= 0) + WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); *msgbuf_ptr++ = ','; --msgbuf_space_left; - if (msgbuf_space_left < 0) - WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); } msgbuf_len = snprintf(msgbuf_ptr, (size_t)msgbuf_space_left, "\"%s\"", res_string); msgbuf_space_left -= msgbuf_len; diff --git a/examples/notification-demo/udp_to_dbus/udp_to_dbus.c b/examples/notification-demo/udp_to_dbus/udp_to_dbus.c index ec7f9c9c..f48c5d31 100644 --- a/examples/notification-demo/udp_to_dbus/udp_to_dbus.c +++ b/examples/notification-demo/udp_to_dbus/udp_to_dbus.c @@ -88,6 +88,7 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con GError *error = NULL; gboolean retval; int ret; + gchar *body_esc = NULL; struct wolfsentry_allocator *allocator = wolfsentry_get_allocator(wolfsentry); @@ -144,6 +145,13 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con local_port = json_value_int32(local_port_v); n_decisions = json_value_array_size(decision_array_v); + if ((action == NULL) || (af == NULL) || (remote_addr == NULL) || + (local_addr == NULL)) { + fprintf(stderr, "ERROR: ignoring malformed notification datagram " + "with missing string field(s)\n"); + break; + } + SNPRINTF_MSGPTR(msgbuf_ptr, msgbuf_len_left, "%s/%d from %s:%d to %s:%d\n", af, proto, @@ -175,12 +183,14 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con SNPRINTF_MSGPTR(msgbuf_ptr, msgbuf_len_left, "]"); - /* xxx note that dbus notification daemons expect fragments of basic - * HTML as input, so msgbuf needs rewriting to escape HTML entities. - */ + /* escape the completed body once, so no entity can be split by the + * fixed-size buffer; the summary is plain text, not markup. */ + body_esc = g_markup_escape_text((const gchar *)msgbuf, -1); + if (body_esc == NULL) + break; summary = action; - body = msgbuf; + body = (const unsigned char *)body_esc; type = "type"; app_name = "wolfsentry_udp_to_dbus"; icon_str = "icon_str"; @@ -206,6 +216,8 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con WARN_ON_JSON_FAILURE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &p_root)); + g_free(body_esc); + if (notify == NULL) return -1; diff --git a/src/action_builtins.c b/src/action_builtins.c index 19cdf255..b4df29c4 100644 --- a/src/action_builtins.c +++ b/src/action_builtins.c @@ -24,6 +24,41 @@ #define WOLFSENTRY_SOURCE_ID WOLFSENTRY_SOURCE_ID_ACTION_BUILTINS_C +/* the public insert wrappers zero action_results before the route table sees + * them, so the CONNECT that spawns a tracking route is counted here instead, + * on exactly the route that was just inserted. caller must hold the lock. + */ +static wolfsentry_errcode_t wolfsentry_builtin_track_peer_count_connect( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t route_id, + const struct wolfsentry_eventconfig_internal *config) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_route *route = NULL; + uint16_t new_count; + + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, route_id, (struct wolfsentry_table_ent_header **)&route); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (route->header.parent_table == NULL) + WOLFSENTRY_ERROR_RETURN(INTERNAL_CHECK_FATAL); + if (route->header.parent_table->ent_type != WOLFSENTRY_OBJECT_TYPE_ROUTE) + WOLFSENTRY_ERROR_RETURN(WRONG_OBJECT); + + if (WOLFSENTRY_CHECK_BITS(WOLFSENTRY_ATOMIC_LOAD(route->flags), WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) + WOLFSENTRY_RETURN_OK; + + /* dispatch counts every connection and enforces the limit only when it is nonzero, so match it. */ + if ((config->config.max_connection_count > 0) && + (WOLFSENTRY_ATOMIC_LOAD(route->meta.connection_count) >= config->config.max_connection_count)) + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(route->meta.connection_count, new_count); + if (new_count == 0) + WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + + WOLFSENTRY_RETURN_OK; +} + /* this simple action creates a new tracking rule, either as a fallthrough or as * a side effect. it can be used to open a pinhole route for bidirectional * traffic, or to track derogatory events on a locally bound port (e.g. crypto @@ -44,10 +79,13 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( { wolfsentry_route_flags_t rule_flags; const struct wolfsentry_event *rule_parent_event = NULL, *rule_aux_event = NULL; + const struct wolfsentry_event *ephemeral_parent_event = NULL; + const struct wolfsentry_eventconfig_internal *ephemeral_config; wolfsentry_addr_bits_t rule_local_addr_len, rule_remote_addr_len; struct wolfsentry_route_exports target_exports; wolfsentry_errcode_t ret; - wolfsentry_action_res_t insert_action_results; + wolfsentry_action_res_t insert_action_results = WOLFSENTRY_ACTION_RES_NONE; + wolfsentry_ent_id_t new_route_id = WOLFSENTRY_ENT_ID_NONE; (void)action; (void)handler_arg; @@ -99,17 +137,31 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( rule_parent_event = wolfsentry_route_parent_event(rule_route); if (rule_parent_event) rule_aux_event = wolfsentry_event_get_aux_event(rule_parent_event); - if (rule_aux_event) { - target_exports.parent_event_label = wolfsentry_event_get_label(rule_aux_event); - target_exports.parent_event_label_len = WOLFSENTRY_LENGTH_NULL_TERMINATED; - } else if (rule_parent_event) { - target_exports.parent_event_label = wolfsentry_event_get_label(rule_parent_event); + if (rule_aux_event) + ephemeral_parent_event = rule_aux_event; + else + ephemeral_parent_event = rule_parent_event; + + if (ephemeral_parent_event != NULL) { + target_exports.parent_event_label = wolfsentry_event_get_label(ephemeral_parent_event); target_exports.parent_event_label_len = WOLFSENTRY_LENGTH_NULL_TERMINATED; } else { target_exports.parent_event_label = NULL; target_exports.parent_event_label_len = 0; } + if ((ephemeral_parent_event != NULL) && (ephemeral_parent_event->config != NULL)) + ephemeral_config = ephemeral_parent_event->config; + else + ephemeral_config = &wolfsentry->config; + + /* an ephemeral route with no purge time escapes both the idle purge and + * max_purgeable_routes, letting a remote peer grow the table without bound. + * skip the insert but return OK so the rest of the match chain still runs. + */ + if (ephemeral_config->config.route_idle_time_for_purge <= 0) + WOLFSENTRY_RETURN_OK; + target_exports.private_data = NULL; target_exports.private_data_size = 0; @@ -117,14 +169,23 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( WOLFSENTRY_RERETURN_IF_ERROR(ret); /* can't pass action_results directly to _route_insert -- it would get zeroed. */ - WOLFSENTRY_WARN_ON_FAILURE( - wolfsentry_route_insert_by_exports_into_table( - WOLFSENTRY_CONTEXT_ARGS_OUT, - route_table, - NULL /* void *caller_arg*/, - &target_exports, - NULL /* id */, - &insert_action_results)); + ret = wolfsentry_route_insert_by_exports_into_table( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route_table, + NULL /* void *caller_arg*/, + &target_exports, + &new_route_id, + &insert_action_results); + WOLFSENTRY_WARN_ON_FAILURE(ret); + /* no route survives a failed insert, so drop INSERTED (it would suppress generator + * accounting); other bits are the chain's decisions and are NONE on early failure. */ + if (ret < 0) { + WOLFSENTRY_CLEAR_BITS(insert_action_results, WOLFSENTRY_ACTION_RES_INSERTED); + } else if (WOLFSENTRY_CHECK_BITS(insert_action_results, WOLFSENTRY_ACTION_RES_INSERTED) && + WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_CONNECT)) + { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_builtin_track_peer_count_connect(WOLFSENTRY_CONTEXT_ARGS_OUT, new_route_id, ephemeral_config)); + } *action_results |= insert_action_results; /* copying over the bits is crucial to inhibit double-accounting for * connection_count, derogatory_count, and commendable_count. */ diff --git a/src/actions.c b/src/actions.c index 755c80e0..50969ec1 100644 --- a/src/actions.c +++ b/src/actions.c @@ -143,12 +143,12 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_insert_1( goto out; if ((ret = wolfsentry_id_allocate(WOLFSENTRY_CONTEXT_ARGS_OUT, &new->header)) < 0) goto out; - if (id) - *id = new->header.id; if ((ret = wolfsentry_table_ent_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, &new->header, &wolfsentry->actions->header, 1 /* unique_p */)) < 0) { ret = WOLFSENTRY_ERROR_RECODE(ret); goto out; } + if (id != NULL) + *id = new->header.id; ret = WOLFSENTRY_ERROR_ENCODE(OK); out: @@ -256,6 +256,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_action_drop_reference(WOLFSENTRY_ if ((action->header.parent_table != NULL) && (action->header.parent_table->ent_type != WOLFSENTRY_OBJECT_TYPE_ACTION)) WOLFSENTRY_ERROR_RETURN(WRONG_OBJECT); + if (action_results) + WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_REFCOUNT_DECREMENT(action->header.refcount, refs_left, ret); WOLFSENTRY_RERETURN_IF_ERROR(ret); if (refs_left > 0) @@ -504,6 +506,9 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_delete( WOLFSENTRY_MUTEX_OR_RETURN(); + if (wolfsentry->action_dispatch_depth != 0) + WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(BUSY); + for (wolfsentry_list_ent_get_first(&action_list->header, &i); i; wolfsentry_list_ent_get_next(&action_list->header, &i)) @@ -534,6 +539,11 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_delete_all( WOLFSENTRY_MUTEX_OR_RETURN(); + /* no dispatch-in-progress guard here: this runs only from event teardown + * (at refcount zero) and clone error cleanup, never on a list being + * iterated, so it must not fail while an unrelated dispatch is active. + */ + for (wolfsentry_list_ent_get_first(&action_list->header, &i); i; i = next) @@ -581,16 +591,6 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( if (WOLFSENTRY_CHECK_BITS(rule_route->flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)) WOLFSENTRY_ERROR_RETURN(ALREADY); - else { - wolfsentry_route_flags_t flags_before; - wolfsentry_route_flags_t flags_after; - WOLFSENTRY_ATOMIC_UPDATE_FLAGS( - rule_route->flags, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, - &flags_before, - &flags_after); - } break; case WOLFSENTRY_ACTION_TYPE_MATCH: @@ -604,16 +604,6 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( if (WOLFSENTRY_CHECK_BITS(rule_route->flags, WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED)) WOLFSENTRY_ERROR_RETURN(ALREADY); - else { - wolfsentry_route_flags_t flags_before; - wolfsentry_route_flags_t flags_after; - WOLFSENTRY_ATOMIC_UPDATE_FLAGS( - rule_route->flags, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, - &flags_before, - &flags_after); - } break; case WOLFSENTRY_ACTION_TYPE_DECISION: @@ -630,6 +620,38 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( (action_event->config && WOLFSENTRY_CHECK_BITS(action_event->config->config.flags, WOLFSENTRY_EVENTCONFIG_FLAG_INHIBIT_ACTIONS))) WOLFSENTRY_RETURN_OK; + /* hold a reference on the event before marking lifecycle state, so a failed + * acquisition cannot leave the route flagged with its handlers never run. + */ + WOLFSENTRY_REFCOUNT_INCREMENT(action_event->header.refcount, ret); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + + if (action_type == WOLFSENTRY_ACTION_TYPE_INSERT) { + wolfsentry_route_flags_t flags_before; + wolfsentry_route_flags_t flags_after; + WOLFSENTRY_ATOMIC_UPDATE_FLAGS( + rule_route->flags, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, + &flags_after); + } + else if (action_type == WOLFSENTRY_ACTION_TYPE_DELETE) { + wolfsentry_route_flags_t flags_before; + wolfsentry_route_flags_t flags_after; + WOLFSENTRY_ATOMIC_UPDATE_FLAGS( + rule_route->flags, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, + &flags_after); + } + + /* mark a dispatch in progress, so a reentrant handler cannot free a live + * list entry (the single-entry mutator returns BUSY). + */ + WOLFSENTRY_ATOMIC_INCREMENT(wolfsentry->action_dispatch_depth, 1); + for (i = (struct wolfsentry_action_list_ent *)w_a_l->header.head; i; i = (struct wolfsentry_action_list_ent *)i->header.next) @@ -641,11 +663,21 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( #ifdef WOLFSENTRY_DEBUG_ACTIONS fprintf(stderr,"calling action %s for event %s and action type %u\n", wolfsentry_action_get_label(i->action), wolfsentry_event_get_label(trigger_event), (unsigned)action_type); #endif - if ((ret = i->action->handler(WOLFSENTRY_CONTEXT_ARGS_OUT, i->action, i->action->handler_arg, caller_arg, trigger_event, action_type, target_route, route_table, rule_route, action_results)) < 0) + if ((ret = i->action->handler(WOLFSENTRY_CONTEXT_ARGS_OUT, i->action, i->action->handler_arg, caller_arg, trigger_event, action_type, target_route, route_table, rule_route, action_results)) < 0) { + WOLFSENTRY_ATOMIC_DECREMENT(wolfsentry->action_dispatch_depth, 1); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action_event, NULL /* action_results */)); WOLFSENTRY_ERROR_RERETURN(ret); - if (WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP)) + } + if (WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP)) { + WOLFSENTRY_ATOMIC_DECREMENT(wolfsentry->action_dispatch_depth, 1); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action_event, NULL /* action_results */)); WOLFSENTRY_RETURN_OK; + } } + + WOLFSENTRY_ATOMIC_DECREMENT(wolfsentry->action_dispatch_depth, 1); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action_event, NULL /* action_results */)); + WOLFSENTRY_RETURN_OK; } diff --git a/src/addr_families.c b/src/addr_families.c index ae58f5cb..3c48e702 100644 --- a/src/addr_families.c +++ b/src/addr_families.c @@ -105,26 +105,23 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( family_byname_len = (int)strlen(family_byname); if (family_byname_len == 0) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); - if (family_byname_len > WOLFSENTRY_MAX_LABEL_BYTES) { - ret = WOLFSENTRY_ERROR_ENCODE(STRING_ARG_TOO_LONG); - goto out; - } + if (family_byname_len > WOLFSENTRY_MAX_LABEL_BYTES) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); #endif - if ((bynumber = (struct wolfsentry_addr_family_bynumber *)WOLFSENTRY_MALLOC(sizeof *bynumber)) == NULL) { - ret = WOLFSENTRY_ERROR_ENCODE(SYS_RESOURCE_FAILED); - goto out; - } + if ((bynumber = (struct wolfsentry_addr_family_bynumber *)WOLFSENTRY_MALLOC(sizeof *bynumber)) == NULL) + WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); + + memset(bynumber, 0, sizeof *bynumber); #ifdef WOLFSENTRY_PROTOCOL_NAMES byname_size = sizeof *byname + (size_t)family_byname_len + 1; if ((byname = (struct wolfsentry_addr_family_byname *)WOLFSENTRY_MALLOC(byname_size)) == NULL) { - ret = WOLFSENTRY_ERROR_ENCODE(SYS_RESOURCE_FAILED); - goto out; + WOLFSENTRY_FREE(bynumber); + WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); } #endif - memset(bynumber, 0, sizeof *bynumber); bynumber->number = family_bynumber; bynumber->parser = parser; bynumber->formatter = formatter; @@ -142,8 +139,13 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( #endif ret = WOLFSENTRY_MUTEX_EX(wolfsentry); - if (ret < 0) - goto out; + if (ret < 0) { + WOLFSENTRY_FREE(bynumber); +#ifdef WOLFSENTRY_PROTOCOL_NAMES + WOLFSENTRY_FREE(byname); +#endif + WOLFSENTRY_ERROR_RERETURN(ret); + } if ((ret = wolfsentry_id_allocate(WOLFSENTRY_CONTEXT_ARGS_OUT, &bynumber->header)) < 0) goto out; @@ -165,6 +167,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( out: + /* reached only after the context lock is held, so unlocking is correct. */ if (ret < 0) { if (bynumber != NULL) { if (bynumber->header.id != WOLFSENTRY_ENT_ID_NONE) @@ -497,6 +500,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_addr_family_pton( */ if (WOLFSENTRY_ATOMIC_LOAD(wolfsentry->addr_families_byname->header.n_ents) > 0) { struct wolfsentry_addr_family_bynumber *addr_family; + wolfsentry_addr_family_t found_number = WOLFSENTRY_AF_UNSPEC; WOLFSENTRY_SHARED_OR_RETURN(); ret = wolfsentry_addr_family_get_byname_1( WOLFSENTRY_CONTEXT_ARGS_OUT, @@ -504,14 +508,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_addr_family_pton( family_name, family_name_len, &addr_family); + if (ret >= 0) + found_number = addr_family->number; WOLFSENTRY_UNLOCK_FOR_RETURN(); if (ret >= 0) { - *family_number = addr_family->number; + *family_number = found_number; WOLFSENTRY_RETURN_OK; } else if (! WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) WOLFSENTRY_ERROR_RERETURN(ret); } + if (strcaseeq(family_name, "UNSPEC", (size_t)family_name_len)) + { *family_number = WOLFSENTRY_AF_UNSPEC; WOLFSENTRY_RETURN_OK; } if (strcaseeq(family_name, "UNIX", (size_t)family_name_len)) { *family_number = WOLFSENTRY_AF_UNIX; WOLFSENTRY_RETURN_OK; } if (strcaseeq(family_name, "LOCAL", (size_t)family_name_len)) diff --git a/src/events.c b/src/events.c index 6d92a88d..1c4c2767 100644 --- a/src/events.c +++ b/src/events.c @@ -77,6 +77,37 @@ static wolfsentry_errcode_t wolfsentry_event_init_1(const char *label, int label WOLFSENTRY_RETURN_OK; } +/* callers hold the context lock, which is what the plain flag write relies on */ +static wolfsentry_errcode_t wolfsentry_event_aux_owner_add(struct wolfsentry_event *aux_event) { + wolfsentry_refcount_t owners; + + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(aux_event->aux_owner_count, owners); + if (owners == 0) + WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + if (! WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) + WOLFSENTRY_SET_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + WOLFSENTRY_RETURN_OK; +} + +/* may run without the context lock when a deleted owner is finally freed, so only the atomic count changes here; the mark is resynced under the lock */ +static wolfsentry_errcode_t wolfsentry_event_aux_owner_remove(struct wolfsentry_event *aux_event) { + wolfsentry_refcount_t owners; + + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY_BY_ONE(aux_event->aux_owner_count, owners); + if (owners == MAX_UINT_OF(aux_event->aux_owner_count)) + WOLFSENTRY_ERROR_RETURN(INTERNAL_CHECK_FATAL); + WOLFSENTRY_RETURN_OK; +} + +static void wolfsentry_event_aux_owner_resync_mark(struct wolfsentry_event *event) { + if ((WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) == 0) && + WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) + { + WOLFSENTRY_CLEAR_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + } + WOLFSENTRY_RETURN_VOID; +} + static void wolfsentry_event_free(WOLFSENTRY_CONTEXT_ARGS_IN, struct wolfsentry_event *event) { WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_delete_all(WOLFSENTRY_CONTEXT_ARGS_OUT, &event->post_action_list)); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_delete_all(WOLFSENTRY_CONTEXT_ARGS_OUT, &event->insert_action_list)); @@ -149,6 +180,10 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_event_clone_bare( memcpy(*new_event, src_event, new_size); WOLFSENTRY_TABLE_ENT_HEADER_RESET(**new_ent); + /* no route in a route-free clone can depend on the copied layout */ + if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)) + WOLFSENTRY_CLEAR_BITS((*new_event)->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); + WOLFSENTRY_LIST_HEADER_RESET((*new_event)->post_action_list.header); WOLFSENTRY_LIST_HEADER_RESET((*new_event)->insert_action_list.header); WOLFSENTRY_LIST_HEADER_RESET((*new_event)->match_action_list.header); @@ -157,6 +192,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_event_clone_bare( WOLFSENTRY_LIST_HEADER_RESET((*new_event)->decision_action_list.header); (*new_event)->aux_event = NULL; + (*new_event)->aux_owner_count = 0; + WOLFSENTRY_CLEAR_BITS((*new_event)->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); if (src_event->config) { if (((*new_event)->config = WOLFSENTRY_MALLOC_1(dest_context->hpi.allocator, sizeof *(*new_event)->config)) == NULL) { @@ -257,6 +294,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_event_clone_resolve( } WOLFSENTRY_REFCOUNT_INCREMENT(new_event->aux_event->header.refcount, ret); WOLFSENTRY_RERETURN_IF_ERROR(ret); + ret = wolfsentry_event_aux_owner_add(new_event->aux_event); + WOLFSENTRY_RERETURN_IF_ERROR(ret); } WOLFSENTRY_RETURN_OK; @@ -289,12 +328,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_insert( wolfsentry_event_free(WOLFSENTRY_CONTEXT_ARGS_OUT, new); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } - if (id) - *id = new->header.id; if ((ret = wolfsentry_table_ent_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, &new->header, &wolfsentry->events->header, 1 /* unique_p */)) < 0) { wolfsentry_event_free(WOLFSENTRY_CONTEXT_ARGS_OUT, new); WOLFSENTRY_ERROR_UNLOCK_AND_RETURN_RECODED(ret); } + if (id) + *id = new->header.id; WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -306,7 +345,17 @@ WOLFSENTRY_API const char *wolfsentry_event_get_label(const struct wolfsentry_ev WOLFSENTRY_API wolfsentry_event_flags_t wolfsentry_event_get_flags(const struct wolfsentry_event *event) { - return event ? event->flags : WOLFSENTRY_EVENT_FLAG_NONE; + wolfsentry_event_flags_t flags; + + if (event == NULL) + return WOLFSENTRY_EVENT_FLAG_NONE; + /* an owner freed without the context lock only drops the count, so derive the mark from it */ + flags = event->flags; + if (WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) > 0) + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + else + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + return flags; } WOLFSENTRY_API const struct wolfsentry_event *wolfsentry_event_get_aux_event(const struct wolfsentry_event *event) @@ -319,7 +368,7 @@ static wolfsentry_errcode_t wolfsentry_event_get_1(WOLFSENTRY_CONTEXT_ARGS_IN, c WOLFSENTRY_STACKBUF( struct wolfsentry_event, label, - WOLFSENTRY_MAX_LABEL_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES + 1, target); struct wolfsentry_event *event_1 = &target.target; @@ -356,27 +405,56 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_config(WOLFSENTRY_CONTE WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } +static int wolfsentry_event_has_routes(const struct wolfsentry_context *wolfsentry, const struct wolfsentry_event *event) { + const struct wolfsentry_table_ent_header *ent; + + if (WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) + return 1; + /* an aux event parents the ephemeral routes its owners generate */ + if (WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) != 0) + return 1; + /* beyond the table's own reference, each live route, deleted or not, holds one */ + if (WOLFSENTRY_ATOMIC_LOAD(event->header.refcount) > 1) + return 1; + for (ent = wolfsentry_table_first(&wolfsentry->routes->header); ent != NULL; ent = ent->next) { + if (((const struct wolfsentry_route *)ent)->parent_event == event) + return 1; + } + return 0; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_update_config(WOLFSENTRY_CONTEXT_ARGS_IN, const char *label, int label_len, const struct wolfsentry_eventconfig *config) { struct wolfsentry_event *event; + const struct wolfsentry_eventconfig_internal *current_config; + struct wolfsentry_eventconfig_internal new_config; wolfsentry_errcode_t ret; + if (config == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); ret = wolfsentry_event_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, label, label_len, &event); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); + ret = wolfsentry_eventconfig_load(config, &new_config); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); + + current_config = (event->config != NULL) ? event->config : &wolfsentry->config; + + /* existing routes were allocated, and are accessed and freed, using the event's current layout */ + if (wolfsentry_event_has_routes(wolfsentry, event) && + ((new_config.config.route_private_data_size != current_config->config.route_private_data_size) || + (new_config.config.route_private_data_alignment != current_config->config.route_private_data_alignment))) + WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(INCOMPATIBLE_STATE); + if (event->config == NULL) { if ((event->config = (struct wolfsentry_eventconfig_internal *)WOLFSENTRY_MALLOC(sizeof *event->config)) == NULL) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(SYS_RESOURCE_FAILED); - if ((ret = wolfsentry_eventconfig_load(config, event->config)) < 0) { - WOLFSENTRY_FREE(event->config); - event->config = NULL; - } - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - } else { - ret = wolfsentry_eventconfig_load(config, event->config); - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } + memcpy(event->config, &new_config, sizeof *event->config); + + WOLFSENTRY_UNLOCK_AND_RETURN_OK; } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_IN, const char *label, int label_len, struct wolfsentry_event **event) { @@ -418,6 +496,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_drop_reference(WOLFSENTRY_C if (refs_left > 0) WOLFSENTRY_RETURN_OK; if (event->aux_event) { + ret = wolfsentry_event_aux_owner_remove(event->aux_event); + WOLFSENTRY_RERETURN_IF_ERROR(ret); ret = wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event->aux_event, NULL); WOLFSENTRY_RERETURN_IF_ERROR(ret); event->aux_event = NULL; @@ -613,45 +693,52 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_set_aux_event( int aux_event_label_len) { wolfsentry_errcode_t ret; - struct wolfsentry_event *event, *aux_event = NULL; + struct wolfsentry_event *event, *aux_event = NULL, *old_aux_event; WOLFSENTRY_MUTEX_OR_RETURN(); ret = wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, event_label_len, &event); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - if (WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) { + /* the owner count is authoritative; a mark left by an owner freed without the lock is cleared here */ + if (WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) != 0) { ret = WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); goto out; } + wolfsentry_event_aux_owner_resync_mark(event); if (aux_event_label) { ret = wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux_event_label, aux_event_label_len, &aux_event); if (ret < 0) { aux_event = NULL; goto out; } - if (WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) { + if ((aux_event == event) || + WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) { ret = WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); goto out; } } else aux_event = NULL; - if (event->aux_event) - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event->aux_event, NULL /* action_results */)); + if (aux_event != NULL) { + ret = wolfsentry_event_aux_owner_add(aux_event); + if (ret < 0) + goto out; + } + + old_aux_event = event->aux_event; event->aux_event = aux_event; + if (old_aux_event != NULL) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_aux_owner_remove(old_aux_event)); + wolfsentry_event_aux_owner_resync_mark(old_aux_event); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old_aux_event, NULL /* action_results */)); + } ret = WOLFSENTRY_ERROR_ENCODE(OK); out: WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event, NULL /* action_results */)); - if (aux_event) { - if (ret < 0) - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux_event, NULL /* action_results */)); - else { - if (! WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) - WOLFSENTRY_SET_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); - } - } + if ((aux_event != NULL) && (ret < 0)) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux_event, NULL /* action_results */)); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } diff --git a/src/json/centijson_dom.c b/src/json/centijson_dom.c index cf880d95..9bc4a69e 100644 --- a/src/json/centijson_dom.c +++ b/src/json/centijson_dom.c @@ -61,7 +61,7 @@ static void *json_malloc(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator), size_t size) { if (allocator) - return allocator->malloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), size); + return allocator->malloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator->context), size); else return NULL; } @@ -141,6 +141,34 @@ init_number( } } +static int +json_dom_path_push(JSON_DOM_PARSER* dom_parser, JSON_VALUE* value) +{ + if(dom_parser->path_size >= dom_parser->path_alloc) { + JSON_VALUE** new_path; + size_t new_path_alloc; + + if(dom_parser->path_alloc == 0) { + new_path_alloc = 32; + } + else if(dom_parser->path_alloc > SIZE_MAX / 2 / sizeof(JSON_VALUE*)) { + return JSON_ERR_OUTOFMEMORY; + } + else { + new_path_alloc = dom_parser->path_alloc * 2; + } + new_path = (JSON_VALUE**) realloc((void *)dom_parser->path, new_path_alloc * sizeof(JSON_VALUE*)); + if(new_path == NULL) + return JSON_ERR_OUTOFMEMORY; + + dom_parser->path = new_path; + dom_parser->path_alloc = new_path_alloc; + } + + dom_parser->path[dom_parser->path_size++] = value; + return 0; +} + int json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, void* user_data) { @@ -154,6 +182,16 @@ json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, vo return 0; } + if(dom_parser->path_size > 0 && + dom_parser->path[dom_parser->path_size - 1] == NULL) + { + /* A NULL path entry marks a duplicate container dropped by USEFIRST; + * swallow its contents so the enclosing nesting state stays correct. */ + if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) + return json_dom_path_push(dom_parser, NULL); + return 0; + } + if(type == JSON_KEY) { /* Object key: We just store it until we get the json_value to use it with. */ if(json_value_init_string_( @@ -201,7 +239,10 @@ json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, vo if(!json_value_is_new(new_json_value)) { /* We have already set json_value for this key. */ switch(dom_parser->flags & JSON_DOM_DUPKEY_MASK) { - case JSON_DOM_DUPKEY_USEFIRST: return 0; + case JSON_DOM_DUPKEY_USEFIRST: + if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) + return json_dom_path_push(dom_parser, NULL); + return 0; case JSON_DOM_DUPKEY_USELAST: ret = json_value_fini( #ifdef WOLFSENTRY @@ -264,28 +305,9 @@ json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, vo if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) { /* Push the array or object to the path, so we know where to * append their json_values. */ - if(dom_parser->path_size >= dom_parser->path_alloc) { - JSON_VALUE** new_path; - size_t new_path_alloc; - - if(dom_parser->path_alloc == 0) { - new_path_alloc = 32; - } - else if(dom_parser->path_alloc > SIZE_MAX / 2 / sizeof(JSON_VALUE*)) { - return JSON_ERR_OUTOFMEMORY; - } - else { - new_path_alloc = dom_parser->path_alloc * 2; - } - new_path = (JSON_VALUE**) realloc((void *)dom_parser->path, new_path_alloc * sizeof(JSON_VALUE*)); - if(new_path == NULL) - return JSON_ERR_OUTOFMEMORY; - - dom_parser->path = new_path; - dom_parser->path_alloc = new_path_alloc; - } - - dom_parser->path[dom_parser->path_size++] = new_json_value; + ret = json_dom_path_push(dom_parser, new_json_value); + if(ret < 0) + return ret; } return 0; diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 1c71d536..0bb42ba6 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -54,6 +54,7 @@ #ifdef CENTIJSON_USE_LOCALE #include /* localeconv() */ #endif +#include /* DBL_MAX */ #include /* snprintf() */ #include #include @@ -404,8 +405,12 @@ json_literal_automaton(JSON_PARSER* parser, const unsigned char* input, size_t s if(off < size && !IS_TOKEN_BOUNDARY(input[off])) { memcpy(&parser->pos, &parser->value_pos, sizeof(JSON_INPUT_POS)); json_raise(parser, JSON_ERR_SYNTAX); - } else if(input == NULL /* EOF */ || - (off < size && IS_TOKEN_BOUNDARY(input[off]))) { + } else if(input == NULL /* EOF */) { + if(parser->substate < literal_size) + json_raise_for_value(parser, JSON_ERR_SYNTAX); + else + json_process(parser, type, NULL, 0); + } else if(off < size && IS_TOKEN_BOUNDARY(input[off])) { json_process(parser, type, NULL, 0); } return off; @@ -980,7 +985,10 @@ json_fini(JSON_PARSER* parser, JSON_INPUT_POS* p_pos) if(parser->automaton != AUTOMATON_MAIN) { json_raise(parser, JSON_ERR_SYNTAX); } - } else if(parser->nesting_level != 0 || !(parser->state & CAN_SEE_EOF)) { + } + + if(parser->errcode >= 0 && + (parser->nesting_level != 0 || !(parser->state & CAN_SEE_EOF))) { json_raise_unexpected(parser); } } @@ -1221,7 +1229,7 @@ json_number_to_double(const unsigned char* num, size_t num_size, double* p_resul * requirements. */ - if(num_size + 1 < sizeof(local_buffer)) { + if(num_size < sizeof(local_buffer)) { /* The number is short enough so we can avoid heap allocation. */ buffer = local_buffer; } else { @@ -1315,11 +1323,7 @@ json_dump_uint64(uint64_t u64, JSON_DUMP_CALLBACK write_func, void* user_data) WOLFSENTRY_API int json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data) { -#if defined(FREERTOS) || (__STDC_VERSION__ < 199901L) - static const char fmt[] = "%.16f"; -#else - static const char fmt[] = "%.16lg"; -#endif + static const char fmt[] = "%.17g"; static const size_t extra_bytes = 2; /* Space reserved for ".0" */ #ifdef CENTIJSON_USE_LOCALE struct lconv* locale_info; @@ -1334,6 +1338,10 @@ json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data) char* fp; int ret; + /* JSON has no representation for infinity or NaN. */ + if(dbl != dbl || dbl > DBL_MAX || dbl < -DBL_MAX) + return write_func((const unsigned char *)"null", 4, user_data); + while(1) { n = snprintf(buffer, capacity, fmt, dbl); if(n >= 0 && (size_t)n < capacity) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 51cf4059..b9d52fa2 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -56,6 +56,7 @@ #endif #include +#include #ifndef WOLFSENTRY_NO_ALLOCA #include #endif @@ -255,6 +256,27 @@ json_value_type(const JSON_VALUE* v) return (JSON_VALUE_TYPE)(v->data.data_bytes[0] & TYPE_MASK); } +static uint64_t json_int_magnitude(int64_t x) +{ + return (x < 0) ? (~(uint64_t)x + 1U) : (uint64_t)x; +} + +/* An integer is exactly representable in a binary floating type with + * mantissa_bits of precision when its significant bits (ignoring trailing + * zeros) fit in that precision. */ +static int json_int_fits_mantissa(uint64_t u, int mantissa_bits) +{ + int bits = 0; + + while((u != 0) && ((u & 1U) == 0)) + u >>= 1; + while(u != 0) { + ++bits; + u >>= 1; + } + return bits <= mantissa_bits; +} + WOLFSENTRY_API int json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) { @@ -265,39 +287,43 @@ json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) * long as the conversion does not loose much information. */ switch(json_value_type(v)) { case JSON_VALUE_INT32: - return (type == JSON_VALUE_INT64 || type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_INT64 || type == JSON_VALUE_DOUBLE) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_int_magnitude(json_value_int32(v)), FLT_MANT_DIG)) || (type == JSON_VALUE_UINT32 && json_value_int32(v) >= 0) || (type == JSON_VALUE_UINT64 && json_value_int32(v) >= 0); case JSON_VALUE_UINT32: - return (type == JSON_VALUE_INT64 || type == JSON_VALUE_UINT64 || type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_INT64 || type == JSON_VALUE_UINT64 || type == JSON_VALUE_DOUBLE) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa((uint64_t)json_value_uint32(v), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_uint32(v) <= INT32_MAX); case JSON_VALUE_INT64: - return (type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_DOUBLE && json_int_fits_mantissa(json_int_magnitude(json_value_int64(v)), DBL_MANT_DIG)) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_int_magnitude(json_value_int64(v)), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_int64(v) >= INT32_MIN && json_value_int64(v) <= INT32_MAX) || (type == JSON_VALUE_UINT32 && json_value_int64(v) >= 0 && json_value_int64(v) <= UINT32_MAX) || (type == JSON_VALUE_UINT64 && json_value_int64(v) >= 0); case JSON_VALUE_UINT64: - return (type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_DOUBLE && json_int_fits_mantissa(json_value_uint64(v), DBL_MANT_DIG)) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_value_uint64(v), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_uint64(v) <= INT32_MAX) || (type == JSON_VALUE_UINT32 && json_value_uint64(v) <= UINT32_MAX) || (type == JSON_VALUE_INT64 && json_value_uint64(v) <= INT64_MAX); case JSON_VALUE_FLOAT: return (type == JSON_VALUE_DOUBLE) || - (type == JSON_VALUE_INT32 && json_value_float(v) == (float)json_value_int32(v)) || - (type == JSON_VALUE_UINT32 && json_value_float(v) == (float)json_value_uint32(v)) || - (type == JSON_VALUE_INT64 && json_value_float(v) == (float)json_value_int64(v)) || - (type == JSON_VALUE_UINT64 && json_value_float(v) == (float)json_value_uint64(v)); + (type == JSON_VALUE_INT32 && (double)json_value_float(v) >= -2147483648.0 && (double)json_value_float(v) <= 2147483647.0 && json_value_float(v) == (float)json_value_int32(v)) || + (type == JSON_VALUE_UINT32 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) <= 4294967295.0 && json_value_float(v) == (float)json_value_uint32(v)) || + (type == JSON_VALUE_INT64 && (double)json_value_float(v) >= -9223372036854775808.0 && (double)json_value_float(v) < 9223372036854775808.0 && json_value_float(v) == (float)json_value_int64(v)) || + (type == JSON_VALUE_UINT64 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) < 18446744073709551616.0 && json_value_float(v) == (float)json_value_uint64(v)); case JSON_VALUE_DOUBLE: - return (type == JSON_VALUE_FLOAT) || - (type == JSON_VALUE_INT32 && json_value_double(v) == (double)json_value_int32(v)) || - (type == JSON_VALUE_UINT32 && json_value_double(v) == (double)json_value_uint32(v)) || - (type == JSON_VALUE_INT64 && json_value_double(v) == (double)json_value_int64(v)) || - (type == JSON_VALUE_UINT64 && json_value_double(v) == (double)json_value_uint64(v)); + return (type == JSON_VALUE_FLOAT && json_value_double(v) >= -(double)FLT_MAX && json_value_double(v) <= (double)FLT_MAX && (double)(float)json_value_double(v) == json_value_double(v)) || /* cppcheck-suppress suspiciousFloatingPointCast */ + (type == JSON_VALUE_INT32 && json_value_double(v) >= -2147483648.0 && json_value_double(v) <= 2147483647.0 && json_value_double(v) == (double)json_value_int32(v)) || + (type == JSON_VALUE_UINT32 && json_value_double(v) >= 0.0 && json_value_double(v) <= 4294967295.0 && json_value_double(v) == (double)json_value_uint32(v)) || + (type == JSON_VALUE_INT64 && json_value_double(v) >= -9223372036854775808.0 && json_value_double(v) < 9223372036854775808.0 && json_value_double(v) == (double)json_value_int64(v)) || + (type == JSON_VALUE_UINT64 && json_value_double(v) >= 0.0 && json_value_double(v) < 18446744073709551616.0 && json_value_double(v) == (double)json_value_uint64(v)); default: break; @@ -320,30 +346,67 @@ json_value_path(JSON_VALUE* root, const char* path) { const unsigned char* token_beg = (const unsigned char *)path; const unsigned char* token_end; + const unsigned char* p; JSON_VALUE* v = root; + size_t path_index; + size_t arr_size; + int negative; + int have_digit; while(1) { token_end = token_beg; while(*token_end != '\0' && *token_end != '/') token_end++; - if(token_end - token_beg > 2 && token_beg[0] == '[' && token_end[-1] == ']') { - size_t path_index = 0; + p = token_beg; + + /* Optional leading dictionary key, up to the first '[' or token end. */ + if(p < token_end && *p != '[') { + const unsigned char* key_end = p; + while(key_end < token_end && *key_end != '[') + key_end++; + v = json_value_dict_get_(v, p, (size_t)(key_end - p)); + if(v == NULL) + return NULL; + p = key_end; + } - token_beg++; - while('0' <= *token_beg && *token_beg <= '9') { - path_index = (path_index * 10U) + (*token_beg - (unsigned)'0'); - token_beg++; + /* Zero or more attached array indexes "[n]", where n may be negative + * (relative to the array length). */ + while(p < token_end && *p == '[') { + p++; + negative = 0; + if(*p == '-') { + negative = 1; + p++; } - if(*token_beg != ']') + path_index = 0; + have_digit = 0; + while('0' <= *p && *p <= '9') { + size_t digit = (size_t)(*p - (unsigned char)'0'); + if(path_index > (SIZE_MAX - digit) / 10U) + return NULL; + path_index = (path_index * 10U) + digit; + have_digit = 1; + p++; + } + if((! have_digit) || (*p != ']')) return NULL; + p++; + if(negative) { + arr_size = json_value_array_size(v); + if((path_index == 0) || (path_index > arr_size)) + return NULL; + path_index = arr_size - path_index; + } v = json_value_array_get(v, path_index); - } else if(token_end - token_beg > 0) { - v = json_value_dict_get_(v, token_beg, (size_t)(token_end - token_beg)); + if(v == NULL) + return NULL; } - if(v == NULL) + /* The token must be fully consumed; reject trailing bytes. */ + if(p != token_end) return NULL; if(*token_end == '\0') @@ -881,6 +944,15 @@ json_value_array_realloc( { JSON_VALUE* json_value_buf; + /* Shrinking to zero: free the buffer directly rather than via realloc(p, 0), + * which is allowed to free and return NULL and would leave a dangling buffer. */ + if(alloc == 0) { + free(a->json_value_buf); + a->json_value_buf = NULL; + a->alloc = 0; + return 0; + } + json_value_buf = (JSON_VALUE*) realloc(a->json_value_buf, alloc * sizeof(JSON_VALUE)); if(json_value_buf == NULL) return -1; @@ -945,16 +1017,21 @@ json_value_array_insert( JSON_VALUE* v, size_t index) { ARRAY* a = json_value_array_payload(v); + size_t new_alloc; if(a == NULL || index > a->size) return NULL; if(a->size >= a->alloc) { + new_alloc = (a->alloc > 0) ? a->alloc * 2 : 1; + /* a wrapped doubling must not reach the shrink-to-zero path */ + if(new_alloc <= a->size) + return NULL; if(json_value_array_realloc( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif - a, (a->alloc > 0) ? a->alloc * 2 : 1) != 0) + a, new_alloc) != 0) return NULL; } @@ -990,12 +1067,16 @@ json_value_array_remove_range( { ARRAY* a = json_value_array_payload(v); size_t i; + size_t end; int ret; - if(a == NULL || index + count > a->size) + /* Validate without adding, so index + count cannot wrap. */ + if(a == NULL || index > a->size || count > a->size - index) return -1; - for(i = index; i < index + count; i++) { + end = index + count; + + for(i = index; i < end; i++) { ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), @@ -1005,9 +1086,9 @@ json_value_array_remove_range( return ret; } - if(index + count < a->size) { - memmove(a->json_value_buf + index, a->json_value_buf + index + count, - (a->size - (index + count)) * sizeof(JSON_VALUE)); + if(end < a->size) { + memmove(a->json_value_buf + index, a->json_value_buf + end, + (a->size - end) * sizeof(JSON_VALUE)); } a->size -= count; @@ -1556,6 +1637,7 @@ json_value_dict_remove_( int path_len = 0; int cmp; int ret; + int ret2; if (node == NULL) return -1; @@ -1673,24 +1755,24 @@ json_value_dict_remove_( else d->order_tail = node->order_prev; } + /* The node is already unlinked, so finalize both members best-effort, then + * always free it and decrement the size to keep the dictionary consistent. */ ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->key); - if (ret < 0) - return ret; - ret = json_value_fini( + ret2 = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->json_value); - if (ret < 0) - return ret; + if (ret == 0) + ret = ret2; free(node); d->size--; - return 0; + return ret; } WOLFSENTRY_API int @@ -1770,48 +1852,53 @@ json_value_dict_clean( JSON_VALUE* v) { DICT* d = json_value_dict_payload(v); - RBTREE **stack; - int stack_size; RBTREE* node; - RBTREE* right; + RBTREE* left; int ret; if(d == NULL) return 0; - stack = (RBTREE **)malloc(rbtree_stack_size_needed(d)); - if (stack == NULL) - return JSON_ERR_OUTOFMEMORY; - - stack_size = json_value_dict_leftmost_path(stack, d->root); - - while(stack_size > 0) { - node = stack[--stack_size]; - right = node->right; + /* Rotate left children up instead of keeping a traversal stack, so cleanup + * cannot fail on allocation; d->root always holds every remaining node. */ + node = d->root; + while(node != NULL) { + if(node->left != NULL) { + left = node->left; + node->left = left->right; + left->right = node; + node = left; + d->root = node; + continue; + } ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->key); - if (ret < 0) { - free(node); - free((void *)stack); + if (ret < 0) return ret; - } ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->json_value); - if (ret < 0) { - free(node); - free((void *)stack); + if (ret < 0) return ret; + d->root = node->right; + if(v->data.data_bytes[0] & HAS_ORDERLIST) { + if(node->order_prev != NULL) + node->order_prev->order_next = node->order_next; + else + d->order_head = node->order_next; + if(node->order_next != NULL) + node->order_next->order_prev = node->order_prev; + else + d->order_tail = node->order_prev; } free(node); - - stack_size += json_value_dict_leftmost_path(stack + stack_size, right); + node = d->root; } if(v->data.data_bytes[0] & HAS_ORDERLIST) @@ -1819,8 +1906,6 @@ json_value_dict_clean( else memset(d, 0, OFFSETOF(DICT, order_head)); - free((void *)stack); - return 0; } /* NOLINTEND(misc-no-recursion) */ @@ -1876,38 +1961,47 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo const ARRAY* src_a = json_value_array_payload((JSON_VALUE *)node); size_t n = json_value_array_size(node); const JSON_VALUE* src_values = json_value_array_get_all(node); - ARRAY* dest_a; - JSON_VALUE* dest_values; + ARRAY* dest_a = NULL; + JSON_VALUE* dest_values = NULL; size_t i; ret = json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), clone); - if (ret < 0) - break; - dest_a = json_value_array_payload(clone); - if (! dest_a) { - ret = JSON_ERR_INTERNAL; - break; + if (ret == 0) { + dest_a = json_value_array_payload(clone); + if (dest_a == NULL) + ret = JSON_ERR_INTERNAL; } - ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), - dest_a, src_a->alloc); - if (ret < 0) - break; + /* Skip the element buffer entirely for an empty array; a zero-size + * allocation may legitimately return NULL. */ + if ((ret == 0) && (src_a->alloc > 0)) + ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), + dest_a, src_a->alloc); - dest_values = json_value_array_get_all(clone); - if (! dest_values) { - ret = JSON_ERR_INTERNAL; - break; + if (ret == 0) { + dest_values = json_value_array_get_all(clone); + if ((n > 0) && (dest_values == NULL)) + ret = JSON_ERR_INTERNAL; } - for(i = 0; i < n; i++) { - ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); - if (ret < 0) - break; + if (ret == 0) { + for(i = 0; i < n; i++) { + json_value_init_null(&dest_values[i]); + dest_a->size = i + 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); + if (ret < 0) + break; + } } - dest_a->size = src_a->size; + /* On any failure finalize the clone so the initialized prefix and + * the array allocation are released, not leaked. */ + if (ret < 0) { + int ret2 = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), clone); + if (ret2 < 0) + WOLFSENTRY_WARN("json_value_fini: %s\n", json_error_str(ret2)); + } break; } @@ -1940,11 +2034,16 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo } } else { int stack_size; - RBTREE **stack = (RBTREE **)malloc(rbtree_stack_size_needed(src_dict)); /* put this on the heap to avoid runaway growth of stack on deep JSON trees. */ - if (! stack) { + RBTREE **stack = NULL; + /* put the stack on the heap to avoid runaway growth of the C + * stack on deep JSON trees; an empty tree needs none, and a + * zero-size allocation may legitimately return NULL. */ + if (src_dict->root != NULL) + stack = (RBTREE **)malloc(rbtree_stack_size_needed(src_dict)); + if ((src_dict->root != NULL) && (stack == NULL)) { ret = JSON_ERR_OUTOFMEMORY; } else { - stack_size = json_value_dict_leftmost_path(stack, src_dict->root); + stack_size = (stack != NULL) ? json_value_dict_leftmost_path(stack, src_dict->root) : 0; while(stack_size > 0) { src_dict_node = stack[--stack_size]; diff --git a/src/json/load_config.c b/src/json/load_config.c index 732f17a9..f691c5e8 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -111,6 +111,9 @@ struct wolfsentry_json_process_state { WOLFSENTRY_SOCKADDR(WOLFSENTRY_MAX_ADDR_BITS) remote; WOLFSENTRY_SOCKADDR(WOLFSENTRY_MAX_ADDR_BITS) local; wolfsentry_route_flags_t flags; + int tcplike_port_numbers_explicit; + int remote_prefix_bits_given; + int local_prefix_bits_given; } route; struct { char label[WOLFSENTRY_MAX_LABEL_BYTES]; @@ -154,7 +157,58 @@ static wolfsentry_errcode_t reset_o_u_c(struct wolfsentry_json_process_state *jp WOLFSENTRY_RETURN_OK; } +/* Range check that does not depend on errno: re-scan the already + * syntactically-validated numeric string in buf (strtol-style base-0 rules) and + * reject it when its magnitude exceeds limit. A leading sign is skipped so the + * caller decides sign handling. + */ +static wolfsentry_errcode_t convert_check_magnitude(const char *buf, uint64_t limit) { + uint64_t acc = 0; + uint64_t base = 10; + const char *p = buf; + + while ((*p == ' ') || (*p == '\t')) + ++p; + if ((*p == '-') || (*p == '+')) + ++p; + if ((p[0] == '0') && ((p[1] == 'x') || (p[1] == 'X'))) { + base = 16; + p += 2; + } else if (p[0] == '0') { + base = 8; + ++p; + } + + while (*p != 0) { + uint64_t digit; + if ((*p >= '0') && (*p <= '9')) + digit = (uint64_t)(*p - '0'); + else if ((*p >= 'a') && (*p <= 'f')) + digit = (uint64_t)(*p - 'a') + 10U; + else if ((*p >= 'A') && (*p <= 'F')) + digit = (uint64_t)(*p - 'A') + 10U; + else + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if (digit >= base) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if (acc > (limit - digit) / base) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + acc = (acc * base) + digit; + ++p; + } + + WOLFSENTRY_RETURN_OK; +} + +/* local equivalent of strspn(p, " \t"), which freestanding targets may lack. */ +static const char *convert_skip_blanks(const char *p) { + while ((*p == ' ') || (*p == '\t')) + ++p; + return p; +} + static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char *data, size_t data_size, uint64_t *out) { + wolfsentry_errcode_t ret; char buf[24]; char *endptr; #ifdef WOLFSENTRY_HAVE_LONG_LONG @@ -172,6 +226,12 @@ static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char * memcpy(buf, data, data_size); buf[data_size] = 0; + /* strtoull()/strtoul() silently negate a leading minus; reject it so a + * negative literal does not wrap to a large unsigned value. + */ + if (*convert_skip_blanks(buf) == '-') + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + #ifndef WOLFSENTRY_NO_ERRNO_H errno = 0; #endif @@ -187,14 +247,19 @@ static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char * if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + ret = convert_check_magnitude(buf, (uint64_t)MAX_UINT_OF(conv)); + WOLFSENTRY_RERETURN_IF_ERROR(ret); *out = (uint64_t)conv; WOLFSENTRY_RETURN_OK; } static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char *data, size_t data_size, int64_t *out) { + wolfsentry_errcode_t ret; char buf[24]; char *endptr; + const char *sign_p; + uint64_t magnitude_limit; #ifdef WOLFSENTRY_HAVE_LONG_LONG long long conv; #else @@ -225,6 +290,13 @@ static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char * if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + sign_p = convert_skip_blanks(buf); + if (*sign_p == '-') + magnitude_limit = (uint64_t)MAX_SINT_OF(conv) + 1U; + else + magnitude_limit = (uint64_t)MAX_SINT_OF(conv); + ret = convert_check_magnitude(buf, magnitude_limit); + WOLFSENTRY_RERETURN_IF_ERROR(ret); *out = (int64_t)conv; WOLFSENTRY_RETURN_OK; @@ -233,7 +305,14 @@ static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char * static wolfsentry_errcode_t convert_uint32(JSON_TYPE type, const unsigned char *data, size_t data_size, uint32_t *out) { char buf[16]; char *endptr; + /* use the widest available unsigned type so the range check below is live + * even on ILP32 ABIs, where unsigned long is the same width as *out. + */ +#ifdef WOLFSENTRY_HAVE_LONG_LONG + unsigned long long conv; +#else unsigned long conv; +#endif /* allow JSON_STRING to accommodate hex and octal values. */ if ((type != JSON_NUMBER) && (type != JSON_STRING)) @@ -247,7 +326,11 @@ static wolfsentry_errcode_t convert_uint32(JSON_TYPE type, const unsigned char * #ifndef WOLFSENTRY_NO_ERRNO_H errno = 0; #endif +#ifdef WOLFSENTRY_HAVE_LONG_LONG + conv = strtoull(buf, &endptr, 0); +#else conv = strtoul(buf, &endptr, 0); +#endif #ifndef WOLFSENTRY_NO_ERRNO_H if (errno != 0) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); @@ -295,7 +378,7 @@ static wolfsentry_errcode_t convert_uint16(JSON_TYPE type, const unsigned char * } static wolfsentry_errcode_t convert_uint8(JSON_TYPE type, const unsigned char *data, size_t data_size, uint8_t *out) { - char buf[4]; + char buf[8]; char *endptr; unsigned long conv; @@ -327,7 +410,8 @@ static wolfsentry_errcode_t convert_uint8(JSON_TYPE type, const unsigned char *d } static wolfsentry_errcode_t convert_double(JSON_TYPE type, const unsigned char *data, size_t data_size, double *out) { - char buf[24]; + /* fits the longest %.17g rendering, 24 characters */ + char buf[25]; char *endptr; double conv; @@ -413,6 +497,12 @@ static wolfsentry_errcode_t convert_wolfsentry_duration(struct wolfsentry_contex } if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + /* durations are non-negative; a negative value (including a LONG_MIN + * saturation when errno is unavailable) would otherwise reach the interval + * conversion and overflow. + */ + if (conv < 0) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if ((ret = wolfsentry_interval_from_seconds(wolfsentry, conv, 0 /* howlong_nsecs */, out)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); else @@ -623,6 +713,13 @@ static inline int convert_hex_byte(const unsigned char **in, size_t *in_len, byt return 0; } +/* a handler registered for AF_LOCAL takes precedence over the built-in literal path */ +static int af_local_is_builtin(struct wolfsentry_json_process_state *jps) +{ + wolfsentry_addr_family_parser_t parser; + return (wolfsentry_addr_family_get_parser(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_AF_LOCAL, &parser) < 0); +} + static wolfsentry_errcode_t convert_sockaddr_address( struct wolfsentry_json_process_state *jps, JSON_TYPE type, @@ -632,6 +729,7 @@ static wolfsentry_errcode_t convert_sockaddr_address( byte *sa_addr) { if ((sa->sa_family == WOLFSENTRY_AF_LINK48) || (sa->sa_family == WOLFSENTRY_AF_LINK64)) { + wolfsentry_addr_family_t requested_family = sa->sa_family; int n; if (type != JSON_STRING) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); @@ -642,6 +740,12 @@ static wolfsentry_errcode_t convert_sockaddr_address( WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); ++n; if (data_size == 0) { + /* an explicit EUI-64 family must not be silently reclassified + * as EUI-48 by the parsed byte count (LINK48 and the generic + * LINK alias share a value, so only LINK64 can be enforced). + */ + if ((requested_family == WOLFSENTRY_AF_LINK64) && (n != 8)) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (n == 6) sa->sa_family = WOLFSENTRY_AF_LINK48; else if (n == 8) @@ -704,7 +808,14 @@ static wolfsentry_errcode_t convert_sockaddr_address( else if (sa->sa_family == WOLFSENTRY_AF_CAN) { /* accept CAN addrs as either strings consisting of hex or octal numbers, or as a JSON-native decimal integer. */ char d_buf[13], *endptr; + /* use the widest available unsigned type so the range check below is + * live even on ILP32 ABIs, where unsigned long equals uint32_t. + */ +#ifdef WOLFSENTRY_HAVE_LONG_LONG + unsigned long long addr; +#else unsigned long addr; +#endif if ((type != JSON_STRING) && (type != JSON_NUMBER)) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (data_size >= sizeof d_buf) @@ -725,7 +836,18 @@ static wolfsentry_errcode_t convert_sockaddr_address( } memcpy(d_buf, data, data_size); d_buf[data_size] = 0; +#ifndef WOLFSENTRY_NO_ERRNO_H + errno = 0; +#endif +#ifdef WOLFSENTRY_HAVE_LONG_LONG + addr = strtoull(d_buf, &endptr, 0); +#else addr = strtoul(d_buf, &endptr, 0); +#endif +#ifndef WOLFSENTRY_NO_ERRNO_H + if (errno != 0) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); +#endif if ((size_t)(endptr - d_buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (addr > MAX_UINT_OF(uint32_t)) @@ -738,6 +860,35 @@ static wolfsentry_errcode_t convert_sockaddr_address( sa->addr_len = 32; WOLFSENTRY_RETURN_OK; } + else if ((sa->sa_family == WOLFSENTRY_AF_LOCAL) && af_local_is_builtin(jps)) { + /* mirror wolfsentry_route_format_address(): the address is a raw path + * stored verbatim, with addr_len recording its length. + */ + size_t i; + if (type != JSON_STRING) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + /* an empty path would be a zero-length, match-all prefix. */ + if (data_size == 0) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if (data_size > (size_t)(WOLFSENTRY_MAX_ADDR_BITS / 8)) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); + /* the route formatter emits the path verbatim between quotes without + * escaping, so reject bytes that would corrupt or inject into that + * dump (quote, backslash, control bytes including NUL). + */ + for (i = 0; i < data_size; ++i) { + if ((data[i] < 0x20) || (data[i] == 0x7f) || (data[i] == '"') || (data[i] == '\\')) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + } + /* addr_len must equal the pathname length, so a prefix-bits supplied + * before the address cannot leave trailing implicit bytes in the dump. + */ + if ((sa->addr_len != 0) && (sa->addr_len != (wolfsentry_addr_bits_t)(data_size * 8))) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + memcpy(sa_addr, data, data_size); + sa->addr_len = (wolfsentry_addr_bits_t)(data_size * 8); + WOLFSENTRY_RETURN_OK; + } else if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) { WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); } @@ -813,6 +964,10 @@ static wolfsentry_errcode_t convert_sockaddr_port_name(struct wolfsentry_json_pr WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); } + /* glibc reports an unknown service with a zero return and a NULL result. */ + if (s == NULL) + WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); + sa->sa_port = (wolfsentry_port_t)ntohs((uint16_t)s->s_port); WOLFSENTRY_RETURN_OK; } @@ -836,13 +991,34 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ else WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); } else if (! strcmp(jps->cur_keyname, "address")) { + wolfsentry_addr_bits_t prior_addr_len = sa->addr_len; + wolfsentry_addr_bits_t max_addr_bits; + int prefix_given = (prior_addr_len != 0); + wolfsentry_errcode_t ret; if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + /* a bitmask also sets addr_len, to the combined address and mask width */ + if (jps->o_u_c.route.flags & (sa == (struct wolfsentry_sockaddr *)&jps->o_u_c.route.remote ? + WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK : + WOLFSENTRY_ROUTE_FLAG_LOCAL_ADDR_BITMASK)) + prefix_given = 0; +#endif WOLFSENTRY_CLEAR_BITS(jps->o_u_c.route.flags, sa == (struct wolfsentry_sockaddr *)&jps->o_u_c.route.remote ? WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD : WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD); - WOLFSENTRY_ERROR_RERETURN(convert_sockaddr_address(jps, type, data, data_size, sa, sa->addr)); + ret = convert_sockaddr_address(jps, type, data, data_size, sa, sa->addr); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + /* an earlier prefix-bits on generic LINK was allowed the EUI-64 width; + * now that the address has settled the family, the prefix must fit it. */ + if (prefix_given && (sa->sa_family == WOLFSENTRY_AF_LINK48)) { + ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, &max_addr_bits); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (prior_addr_len > max_addr_bits) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + } + WOLFSENTRY_RETURN_OK; } #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING else if (! strcmp(jps->cur_keyname, "bitmask")) { @@ -852,6 +1028,14 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); + /* the right-justify below would keep only the prefix-sized leading bytes of a full address */ + if ((jps->section_under_construction == S_U_C_REMOTE_ENDPOINT) ? + jps->o_u_c.route.remote_prefix_bits_given : + jps->o_u_c.route.local_prefix_bits_given) + { + WOLFSENTRY_ERROR_RETURN(CONFIG_MISPLACED_KEY); + } + ret = wolfsentry_addr_family_max_addr_bits( JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, @@ -868,8 +1052,14 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ * move it to right-justified position. */ if ((sa->addr_len != 0) && (sa->addr_len != addr_size)) { - size_t pad_bytes = WOLFSENTRY_BITS_TO_BYTES((size_t)addr_size - (size_t)sa->addr_len); - memmove(sa->addr + pad_bytes, sa->addr, sa->addr_len); + size_t addr_bytes; + size_t pad_bytes; + if (sa->addr_len > addr_size) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + /* addr_len is a bit count; the move operates on whole bytes. */ + addr_bytes = WOLFSENTRY_BITS_TO_BYTES((size_t)sa->addr_len); + pad_bytes = WOLFSENTRY_BITS_TO_BYTES((size_t)addr_size) - addr_bytes; + memmove(sa->addr + pad_bytes, sa->addr, addr_bytes); memset(sa->addr, 0, pad_bytes); } @@ -886,6 +1076,10 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ } #endif else if (! strcmp(jps->cur_keyname, "prefix-bits")) { + wolfsentry_addr_bits_t max_addr_bits; + wolfsentry_addr_bits_t prior_addr_len = sa->addr_len; + wolfsentry_addr_family_t width_family = sa->sa_family; + wolfsentry_errcode_t ret; if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING @@ -898,7 +1092,40 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ WOLFSENTRY_ERROR_RETURN(CONFIG_MISPLACED_KEY); } #endif - WOLFSENTRY_ERROR_RERETURN(convert_uint16(type, data, data_size, &sa->addr_len)); + ret = convert_uint16(type, data, data_size, &sa->addr_len); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + /* bound prefix-bits to the address family width so a later copy cannot + * read past the fixed-size staging buffer. AF_LOCAL path addresses have + * no fixed family width (and no entry in the family table), so bound + * them by the staging buffer, matching the formatter's dump output. + */ + if ((sa->sa_family == WOLFSENTRY_AF_LOCAL) && af_local_is_builtin(jps)) { + max_addr_bits = (wolfsentry_addr_bits_t)((WOLFSENTRY_MAX_ADDR_BITS / 8) * 8); + /* an AF_LOCAL prefix-bits only denotes the pathname length, so it + * must be nonzero whole bytes and match any pathname already parsed; + * keeping it nonzero leaves addr_len == 0 meaning "not supplied". + */ + if ((sa->addr_len == 0) || (sa->addr_len & 0x7)) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if ((prior_addr_len != 0) && (sa->addr_len != prior_addr_len)) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + } + else { + /* generic LINK becomes EUI-48 or EUI-64 from the address length, so + * before an address arrives allow the wider width; the address + * handler then checks the prefix against the settled family. */ + if ((width_family == WOLFSENTRY_AF_LINK48) && (prior_addr_len == 0)) + width_family = WOLFSENTRY_AF_LINK64; + ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, width_family, &max_addr_bits); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } + if (sa->addr_len > max_addr_bits) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + if (jps->section_under_construction == S_U_C_REMOTE_ENDPOINT) + jps->o_u_c.route.remote_prefix_bits_given = 1; + else + jps->o_u_c.route.local_prefix_bits_given = 1; + WOLFSENTRY_RETURN_OK; } else if (! strcmp(jps->cur_keyname, "interface")) { WOLFSENTRY_CLEAR_BITS(jps->o_u_c.route.flags, @@ -981,6 +1208,10 @@ static wolfsentry_errcode_t handle_route_protocol_clause(struct wolfsentry_json_ WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); } + /* glibc reports an unknown protocol with a zero return and a NULL result. */ + if (p == NULL) + WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); + jps->o_u_c.route.remote.sa_proto = (wolfsentry_proto_t)p->p_proto; jps->o_u_c.route.local.sa_proto = (wolfsentry_proto_t)p->p_proto; } @@ -988,7 +1219,9 @@ static wolfsentry_errcode_t handle_route_protocol_clause(struct wolfsentry_json_ else WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); - if (((jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET) || (jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET6)) && + /* implicit default only; an explicit tcplike-port-numbers setting wins. */ + if ((! jps->o_u_c.route.tcplike_port_numbers_explicit) && + ((jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET) || (jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET6)) && ((jps->o_u_c.route.remote.sa_proto == IPPROTO_TCP) || (jps->o_u_c.route.remote.sa_proto == IPPROTO_UDP))) WOLFSENTRY_SET_BITS(jps->o_u_c.route.flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS); @@ -1071,8 +1304,12 @@ static wolfsentry_errcode_t handle_route_clause(struct wolfsentry_json_process_s else { wolfsentry_route_flags_t flag; ret = wolfsentry_route_flag_assoc_by_name(jps->cur_keyname, WOLFSENTRY_LENGTH_NULL_TERMINATED, &flag); - if (ret >= 0) - WOLFSENTRY_ERROR_RERETURN(handle_route_boolean_clause(type, &jps->o_u_c.route.flags, flag)); + if (ret >= 0) { + ret = handle_route_boolean_clause(type, &jps->o_u_c.route.flags, flag); + if ((ret >= 0) && (flag == WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)) + jps->o_u_c.route.tcplike_port_numbers_explicit = 1; + WOLFSENTRY_ERROR_RERETURN(ret); + } else { if (WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_KEY); @@ -1100,7 +1337,7 @@ static wolfsentry_errcode_t handle_event_clause(struct wolfsentry_json_process_s } if ((jps->cur_depth == 3) && (type == JSON_STRING) && (! strcmp(jps->cur_keyname, "label"))) { - if (data_size >= sizeof jps->o_u_c.event.label) + if (data_size > sizeof jps->o_u_c.event.label) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); memcpy(jps->o_u_c.event.label, data, data_size); jps->o_u_c.event.label_len = (int)data_size; @@ -1632,7 +1869,7 @@ static wolfsentry_errcode_t json_process( WOLFSENTRY_RETURN_OK; } if (! strcmp(jps->cur_keyname, "actions-update")) { - WOLFSENTRY_ERROR_RETURN(CONFIG_MISSING_HANDLER); + WOLFSENTRY_ERROR_OUT(CONFIG_MISSING_HANDLER); /* jps->table_under_construction = T_U_C_ACTIONS; WOLFSENTRY_RETURN_OK; @@ -1735,7 +1972,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_init_ex( #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS .max_total_len = WOLFSENTRY_MAX_JSON_TOTAL_LEN, .max_total_values = 0, - .max_number_len = 20, + .max_number_len = 24, .max_string_len = WOLFSENTRY_KV_MAX_VALUE_BYTES, .max_key_len = WOLFSENTRY_MAX_LABEL_BYTES, .max_nesting_level = WOLFSENTRY_MAX_JSON_NESTING, @@ -1743,7 +1980,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_init_ex( #else WOLFSENTRY_MAX_JSON_TOTAL_LEN, 0, - 20, + 24, WOLFSENTRY_KV_MAX_VALUE_BYTES, WOLFSENTRY_MAX_LABEL_BYTES, WOLFSENTRY_MAX_JSON_NESTING, @@ -2008,13 +2245,21 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_fini( if (full_cycle_of_insert_actions_p) { wolfsentry_action_res_t action_results = WOLFSENTRY_ACTION_RES_NONE; - if ((ret = wolfsentry_route_bulk_clear_insert_action_status(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, &action_results)) < 0) - goto out; - if ((ret = wolfsentry_context_flush(JPSP_WOLFSENTRY_CONTEXT_ARGS_OUT)) < 0) + ret = wolfsentry_route_bulk_clear_insert_action_status(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, &action_results); + if (ret >= 0) + ret = wolfsentry_context_flush(JPSP_WOLFSENTRY_CONTEXT_ARGS_OUT); + /* the candidate is already live after the exchange; re-enable its + * actions even if the preceding steps failed, so a post-exchange + * failure cannot leave the running configuration action-inhibited. + * keep the first error. + */ + if (ret >= 0) + ret = wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT); + else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT)); + if (ret < 0) goto out; - } - - if ((ret = wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT)) < 0) + } else if ((ret = wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT)) < 0) goto out; { @@ -2036,6 +2281,26 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_fini( if ((*jps)->wolfsentry && ((*jps)->wolfsentry != (*jps)->wolfsentry_actual)) WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free(JPSP_P_WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* In the default (non-transactional) load mode the live context was flushed + * before parsing; a failure leaves it partially populated while its default + * policy is unchanged. Fail closed so a half-loaded policy cannot admit + * traffic: clear the routes and set a REJECT default policy. + */ + if ((ret < 0) && + (! WOLFSENTRY_MASKIN_BITS((*jps)->load_flags, + WOLFSENTRY_CONFIG_LOAD_FLAG_DRY_RUN | + WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT | + WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH))) + { + struct wolfsentry_route_table *main_table; + wolfsentry_action_res_t action_results; + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + if (wolfsentry_route_get_main_table(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, &main_table) >= 0) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_flush_table(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, main_table, &action_results)); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_table_default_policy_set(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, main_table, WOLFSENTRY_ACTION_RES_REJECT)); + } + } + #ifdef WOLFSENTRY_THREADSAFE { wolfsentry_errcode_t _lock_ret; diff --git a/src/kv.c b/src/kv.c index d921ad5d..c453f2fe 100644 --- a/src/kv.c +++ b/src/kv.c @@ -26,6 +26,9 @@ #include #endif +#include +#include + #define WOLFSENTRY_SOURCE_ID WOLFSENTRY_SOURCE_ID_KV_C static inline int wolfsentry_kv_key_cmp_1(const char *left_label, const unsigned int left_label_len, const char *right_label, const unsigned int right_label_len) { @@ -60,7 +63,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_new( struct wolfsentry_kv_pair_internal **kv) { size_t kv_size; - if (key_len == 0) + if (key == NULL) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (data_len < 0) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); @@ -68,6 +71,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_new( WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); if (key_len < 0) key_len = (int)strlen(key); + if (key_len <= 0) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (key_len > WOLFSENTRY_MAX_LABEL_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); kv_size = sizeof **kv + (size_t)key_len + 1 + (size_t)data_len; @@ -170,9 +175,14 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_insert( struct wolfsentry_kv_pair_internal *kv) { wolfsentry_errcode_t ret; + wolfsentry_kv_validator_t validator = WOLFSENTRY_ATOMIC_LOAD(kv_table->validator); - if (kv_table->validator) { - ret = kv_table->validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); + /* read the validator pointer atomically (paired with the atomic store in + * the setter) but call it without the context lock held, to avoid a + * lock-order inversion with locks the application callback may take. + */ + if (validator) { + ret = validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); WOLFSENTRY_RERETURN_IF_ERROR(ret); } @@ -224,9 +234,14 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set( { struct wolfsentry_kv_pair_internal *old; wolfsentry_errcode_t ret; + wolfsentry_kv_validator_t validator = WOLFSENTRY_ATOMIC_LOAD(kv_table->validator); - if (kv_table->validator) { - ret = kv_table->validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); + /* read the validator pointer atomically (paired with the atomic store in + * the setter) but call it without the context lock held, to avoid a + * lock-order inversion with locks the application callback may take. + */ + if (validator) { + ret = validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); WOLFSENTRY_RERETURN_IF_ERROR(ret); } @@ -242,11 +257,23 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set( WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); WOLFSENTRY_UNLOCK_AND_RETURN_OK; } - if (((ret = wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &old->header)) < 0) || - ((ret = wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)) < 0)) - { + /* reserve the replacement ID first, so an ID allocation failure leaves the old entry in place. */ + if ((ret = wolfsentry_id_allocate(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header)) < 0) + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + if ((ret = wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &old->header)) < 0) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header)); + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } + if ((ret = wolfsentry_table_ent_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header, &kv_table->header, 1 /* unique_p */)) < 0) { + /* the old ID was released by the delete, so restoring needs a fresh one; drop the old entry if that fails. */ + if (kv->header.id != WOLFSENTRY_ENT_ID_NONE) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header)); + if (wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, old) < 0) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)); + WOLFSENTRY_UNLOCK_AND_RETURN_OK; } ret = wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv); @@ -257,6 +284,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_1( WOLFSENTRY_CONTEXT_ARGS_IN, struct wolfsentry_kv_table *kv_table, const struct wolfsentry_kv_pair_internal *kv_template, + int take_reference, struct wolfsentry_kv_pair_internal **kv) { wolfsentry_errcode_t ret; @@ -272,6 +300,13 @@ static wolfsentry_errcode_t wolfsentry_kv_get_1( else if ((WOLFSENTRY_KV_TYPE(&kv_template->kv) != WOLFSENTRY_KV_NONE) && (WOLFSENTRY_KV_TYPE(&kv_template->kv) != WOLFSENTRY_KV_TYPE(&ret_kv->kv))) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(WRONG_TYPE); + /* take the reference while the shared lock is still held, so a concurrent + * delete cannot free the entry between lookup and increment. + */ + if (take_reference) { + WOLFSENTRY_REFCOUNT_INCREMENT(ret_kv->header.refcount, ret); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); + } *kv = ret_kv; WOLFSENTRY_UNLOCK_AND_RETURN_OK; } @@ -289,7 +324,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_2( if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); kv_template->kv.v_type = type; - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, kv); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 1 /* take_reference */, kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_ERROR_RERETURN(ret); } @@ -300,11 +335,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_reference_1( const struct wolfsentry_kv_pair_internal *kv_template, struct wolfsentry_kv_pair_internal **kv) { - wolfsentry_errcode_t ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, kv); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - WOLFSENTRY_REFCOUNT_INCREMENT((*kv)->header.refcount, ret); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 1 /* take_reference */, kv)); } WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_reference( @@ -318,7 +349,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_reference( struct wolfsentry_kv_pair_internal *kv_template; wolfsentry_errcode_t ret; if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + WOLFSENTRY_ERROR_RERETURN(ret); kv_template->kv.v_type = type; ret = wolfsentry_kv_get_reference_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); @@ -338,11 +369,11 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_type( struct wolfsentry_kv_pair_internal *kv = NULL; if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, &kv); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 1 /* take_reference */, &kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_RERETURN_IF_ERROR(ret); *type = WOLFSENTRY_KV_TYPE(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_type_to_string( @@ -402,6 +433,111 @@ static int _json_value_dump_writer(const unsigned char* str, size_t size, void * } #endif +#ifndef WOLFSENTRY_HAVE_JSON_DOM + +#define WOLFSENTRY_KV_EMIT_CHAR(c) do { if (needed < out_space) out[needed] = (char)(c); ++needed; } while (0) + +/* escape a KV string into a JSON string literal, mirroring json_dump_string in + * the DOM path, and return the snprintf-style length (excluding the null). + */ +static int wolfsentry_kv_render_string_escaped( + const char *in, + size_t in_len, + char *out, + size_t out_space) +{ + static const char hexdigits[] = "0123456789abcdef"; + size_t in_i; + size_t needed = 0; + unsigned char c; + + WOLFSENTRY_KV_EMIT_CHAR('"'); + for (in_i = 0; in_i < in_len; ++in_i) { + c = (unsigned char)in[in_i]; + switch (c) { + case '"': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('"'); + break; + case '\\': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('\\'); + break; + case '\n': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('n'); + break; + case '\r': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('r'); + break; + case '\t': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('t'); + break; + case '\b': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('b'); + break; + case '\f': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('f'); + break; + default: + if (c < 0x20) { + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('u'); + WOLFSENTRY_KV_EMIT_CHAR('0'); + WOLFSENTRY_KV_EMIT_CHAR('0'); + WOLFSENTRY_KV_EMIT_CHAR(hexdigits[(c >> 4) & 0xf]); + WOLFSENTRY_KV_EMIT_CHAR(hexdigits[c & 0xf]); + } else { + WOLFSENTRY_KV_EMIT_CHAR(c); + } + break; + } + } + WOLFSENTRY_KV_EMIT_CHAR('"'); + + if (out_space > 0) { + if (needed < out_space) + out[needed] = 0; + else + out[out_space - 1] = 0; + } + + return (int)needed; +} + +#undef WOLFSENTRY_KV_EMIT_CHAR + +#endif /* !WOLFSENTRY_HAVE_JSON_DOM */ + +/* snprintf() semantics; C89 has no portable 64-bit conversion specifier. */ +static int wolfsentry_kv_render_uint64(uint64_t magnitude, int negative, char *out, size_t out_space) +{ + char digits[20]; + size_t n_digits = 0; + size_t needed; + size_t pos = 0; + + do { + digits[n_digits++] = (char)('0' + (int)(magnitude % 10U)); + magnitude /= 10U; + } while (magnitude != 0); + needed = n_digits + (negative ? 1U : 0U); + + if (out_space > 0) { + if (negative && (pos < out_space - 1)) + out[pos++] = '-'; + while ((n_digits > 0) && (pos < out_space - 1)) + out[pos++] = digits[--n_digits]; + out[pos] = 0; + } + + return (int)needed; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_kv_pair *kv, @@ -425,17 +561,36 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( *out_len = snprintf(out, out_space, "false"); break; case WOLFSENTRY_KV_UINT: - *out_len = snprintf(out, out_space, "%lu", (long unsigned int)WOLFSENTRY_KV_V_UINT(kv)); + *out_len = wolfsentry_kv_render_uint64(WOLFSENTRY_KV_V_UINT(kv), 0, out, out_space); break; - case WOLFSENTRY_KV_SINT: - *out_len = snprintf(out, out_space, "%ld", (long int)WOLFSENTRY_KV_V_SINT(kv)); + case WOLFSENTRY_KV_SINT: { + int64_t v_sint = WOLFSENTRY_KV_V_SINT(kv); + if (v_sint < 0) + *out_len = wolfsentry_kv_render_uint64((uint64_t)(-(v_sint + 1)) + 1U, 1, out, out_space); + else + *out_len = wolfsentry_kv_render_uint64((uint64_t)v_sint, 0, out, out_space); break; - case WOLFSENTRY_KV_FLOAT: - *out_len = snprintf(out, out_space, "%.10f", WOLFSENTRY_KV_V_FLOAT(kv)); + } + case WOLFSENTRY_KV_FLOAT: { + double v_float = WOLFSENTRY_KV_V_FLOAT(kv); + /* non-finite values have no bare JSON token, so emit a quoted string + * that the loader's strtod path re-reads to the same value. finiteness + * is tested with DBL_MAX comparisons to stay legal under C89 (no C99 + * isnan()/isinf()). + */ + if (!(v_float <= DBL_MAX) && !(v_float >= -DBL_MAX)) /* NaN */ + *out_len = snprintf(out, out_space, "\"nan\""); + else if (!(v_float <= DBL_MAX)) /* +Inf */ + *out_len = snprintf(out, out_space, "\"inf\""); + else if (!(v_float >= -DBL_MAX)) /* -Inf */ + *out_len = snprintf(out, out_space, "\"-inf\""); + else + *out_len = snprintf(out, out_space, "%.17g", v_float); break; + } case WOLFSENTRY_KV_STRING: { #ifndef WOLFSENTRY_HAVE_JSON_DOM - *out_len = snprintf(out, out_space, "\"%.*s\"", (int)WOLFSENTRY_KV_V_STRING_LEN(kv), WOLFSENTRY_KV_V_STRING(kv)); + *out_len = wolfsentry_kv_render_string_escaped(WOLFSENTRY_KV_V_STRING(kv), WOLFSENTRY_KV_V_STRING_LEN(kv), out, out_space); break; #else struct dump_buf_state dbs; @@ -545,7 +700,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_delete( if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, &old); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 0 /* take_reference */, &old); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); if (old->kv.v_type & WOLFSENTRY_KV_FLAG_READONLY) @@ -591,7 +746,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set_validator( ret = wolfsentry_table_map(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_table->header, (wolfsentry_map_function_t)apply_validator, (void *)&context, action_results); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } - wolfsentry->user_values->validator = validator; + WOLFSENTRY_ATOMIC_STORE(kv_table->validator, validator); WOLFSENTRY_UNLOCK_AND_RETURN_OK; } @@ -713,7 +868,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_set_mutability( struct wolfsentry_kv_pair_internal *kv = NULL; if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_NONE, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); - WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_set_mutability(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, kv, mutable)); + ret = wolfsentry_kv_set_mutability(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, kv, mutable); + if (ret < 0) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); + else + ret = wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL); + WOLFSENTRY_ERROR_RERETURN(ret); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_mutability( @@ -727,7 +887,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_mutability( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_NONE, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *mutable = wolfsentry_kv_get_mutability(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_type( @@ -805,10 +965,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_bool( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_NONE, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); if ((WOLFSENTRY_KV_TYPE(&kv->kv) != WOLFSENTRY_KV_TRUE) && - (WOLFSENTRY_KV_TYPE(&kv->kv) != WOLFSENTRY_KV_FALSE)) + (WOLFSENTRY_KV_TYPE(&kv->kv) != WOLFSENTRY_KV_FALSE)) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); WOLFSENTRY_ERROR_RETURN(WRONG_TYPE); + } *value = WOLFSENTRY_KV_TYPE(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_uint( @@ -845,7 +1007,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_uint( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_UINT, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *value = WOLFSENTRY_KV_V_UINT(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_sint( @@ -883,7 +1045,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_sint( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_SINT, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *value = WOLFSENTRY_KV_V_SINT(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_double( @@ -921,7 +1083,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_float( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_FLOAT, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *value = WOLFSENTRY_KV_V_FLOAT(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_string( @@ -934,8 +1096,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_string( { wolfsentry_errcode_t ret; struct wolfsentry_kv_pair_internal *kv; - if (value_len < 0) - value_len = (int)strlen(value); + if (value_len < 0) { + size_t value_strlen = strlen(value); + if (value_strlen > (size_t)WOLFSENTRY_KV_MAX_VALUE_BYTES) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); + value_len = (int)value_strlen; + } if (value_len > WOLFSENTRY_KV_MAX_VALUE_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, value_len + 1, &kv)) < 0) @@ -1008,8 +1174,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_bytes_base64( wolfsentry_errcode_t ret; int decoded_len; struct wolfsentry_kv_pair_internal *kv; - if (value_len < 0) - value_len = (int)strlen(value); + /* the decoded size computation adds 3 to the length */ + if (value_len < 0) { + size_t value_strlen = strlen(value); + if (value_strlen > (size_t)(INT_MAX - 3)) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); + value_len = (int)value_strlen; + } else if (value_len > INT_MAX - 3) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); decoded_len = WOLFSENTRY_BASE64_DECODED_BUFSPC(value, value_len); if (decoded_len > WOLFSENTRY_KV_MAX_VALUE_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index f8d3b603..a7cca6d1 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -105,7 +105,7 @@ static err_t ethernet_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -186,7 +186,10 @@ static err_t ethernet_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -242,7 +245,7 @@ static err_t ip4_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -326,7 +329,10 @@ static err_t ip4_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -377,7 +383,7 @@ static err_t ip6_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -461,7 +467,10 @@ static err_t ip6_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -515,7 +524,7 @@ static err_t tcp_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_ACCEPTING: @@ -540,6 +549,7 @@ static err_t tcp_filter_with_wolfsentry( route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; else route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; + action_results = WOLFSENTRY_ACTION_RES_CLOSE_WAIT; break; case FILT_PORT_UNREACHABLE: route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; @@ -662,7 +672,10 @@ static err_t tcp_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) ret = ERR_RST; else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; @@ -727,7 +740,7 @@ static err_t udp_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_BINDING: @@ -851,7 +864,10 @@ static err_t udp_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) ret = ERR_RST; else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; @@ -914,7 +930,7 @@ static err_t icmp4_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -999,7 +1015,10 @@ static err_t icmp4_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -1050,7 +1069,7 @@ static err_t icmp6_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -1135,7 +1154,10 @@ static err_t icmp6_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -1148,8 +1170,118 @@ static err_t icmp6_filter_with_wolfsentry( #endif /* LWIP_ICMP6 */ +#define LWIP_FILTER_SLOT_ETHERNET (1U << 0) +#define LWIP_FILTER_SLOT_IP (1U << 1) +#define LWIP_FILTER_SLOT_ICMP (1U << 2) +#define LWIP_FILTER_SLOT_TCP (1U << 3) +#define LWIP_FILTER_SLOT_UDP (1U << 4) + +/* lwIP has no getters for its filter slots, so the owning context is tracked + * here. Contexts hold only their own mutex, so ownership changes atomically + * and only the owner touches the slots and the lwIP setters. */ +static struct wolfsentry_context *lwip_filter_owner = NULL; +static unsigned int lwip_filter_slots = 0; + +static wolfsentry_errcode_t lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_IN, unsigned int slot) +{ + wolfsentry_errcode_t ret; +#ifdef WOLFSENTRY_THREADSAFE + struct wolfsentry_context *expected = NULL; + + if ((! WOLFSENTRY_ATOMIC_CMPXCHG(&lwip_filter_owner, &expected, wolfsentry, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) && + (expected != wolfsentry)) + { + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + } +#else + if ((lwip_filter_owner != NULL) && (lwip_filter_owner != wolfsentry)) + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + lwip_filter_owner = wolfsentry; +#endif + ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + if (ret < 0) { + if (lwip_filter_slots == 0) + WOLFSENTRY_ATOMIC_STORE(lwip_filter_owner, NULL); + WOLFSENTRY_ERROR_RERETURN(ret); + } + lwip_filter_slots |= slot; + WOLFSENTRY_RETURN_OK; +} + +static void lwip_filter_clear_slot(unsigned int slot) +{ + switch (slot) { + case LWIP_FILTER_SLOT_ETHERNET: +#if LWIP_ARP || LWIP_ETHERNET + ethernet_filter(NULL); + ethernet_filter_mask(0); + ethernet_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_IP: +#if LWIP_IPV4 + ip4_filter(NULL); + ip4_filter_mask(0); + ip4_filter_arg(NULL); +#endif +#if LWIP_IPV6 + ip6_filter(NULL); + ip6_filter_mask(0); + ip6_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_ICMP: +#if LWIP_ICMP + icmp_filter(NULL); + icmp_filter_mask(0); + icmp_filter_arg(NULL); +#endif +#if LWIP_ICMP6 + icmp6_filter(NULL); + icmp6_filter_mask(0); + icmp6_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_TCP: +#if LWIP_TCP + tcp_filter(NULL); + tcp_filter_mask(0); + tcp_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_UDP: +#if LWIP_UDP + udp_filter(NULL); + udp_filter_mask(0); + udp_filter_arg(NULL); +#endif + break; + } +} + +static wolfsentry_errcode_t lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_IN, unsigned int slot) +{ + struct wolfsentry_context *owner = WOLFSENTRY_ATOMIC_LOAD(lwip_filter_owner); + + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + /* with no owner, no wolfSentry callbacks are installed, so there is nothing to clear. */ + if (owner == NULL) + WOLFSENTRY_RETURN_OK; + if (owner != wolfsentry) + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + lwip_filter_clear_slot(slot); + lwip_filter_slots &= ~slot; + /* ownership is released only after the last slot is fully cleared. */ + if (lwip_filter_slots == 0) + WOLFSENTRY_ATOMIC_STORE(lwip_filter_owner, NULL); + WOLFSENTRY_RETURN_OK; +} + WOLFSENTRY_API_VOID wolfsentry_cleanup_lwip_filter_callbacks(WOLFSENTRY_CONTEXT_ARGS_IN, void *cleanup_arg) { (void)cleanup_arg; + /* a destroyed non-owner must not disable the surviving owner's filters. */ + if (WOLFSENTRY_ATOMIC_LOAD(lwip_filter_owner) != wolfsentry) + return; WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_install_lwip_filter_callbacks(WOLFSENTRY_CONTEXT_ARGS_OUT, 0, 0, 0, 0, 0)); } @@ -1158,17 +1290,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ethernet_call packet_filter_event_mask_t ethernet_mask) { #if LWIP_ARP || LWIP_ETHERNET + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); if (ethernet_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ETHERNET); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - ethernet_filter(ethernet_filter_with_wolfsentry); - ethernet_filter_mask(ethernet_mask); ethernet_filter_arg((void *)wolfsentry); + ethernet_filter_mask(ethernet_mask); + ethernet_filter(ethernet_filter_with_wolfsentry); } else { - ethernet_filter(NULL); - ethernet_filter_mask(0); - ethernet_filter_arg(NULL); + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ETHERNET); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } WOLFSENTRY_UNLOCK_AND_RETURN_OK; #else @@ -1184,33 +1317,28 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ip_callbacks( packet_filter_event_mask_t ip_mask) { #if LWIP_IPV4 || LWIP_IPV6 + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); - if (ip_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); - WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - } + if (ip_mask) + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_IP); + else + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_IP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); #endif #if LWIP_IPV4 if (ip_mask) { - ip4_filter(ip4_filter_with_wolfsentry); - ip4_filter_mask(ip_mask); ip4_filter_arg((void *)wolfsentry); - } else { - ip4_filter(NULL); - ip4_filter_mask(0); - ip4_filter_arg(NULL); + ip4_filter_mask(ip_mask); + ip4_filter(ip4_filter_with_wolfsentry); } #endif /* LWIP_IPV4 */ #if LWIP_IPV6 if (ip_mask) { - ip6_filter(ip6_filter_with_wolfsentry); - ip6_filter_mask(ip_mask); ip6_filter_arg((void *)wolfsentry); - } else { - ip6_filter(NULL); - ip6_filter_mask(0); - ip6_filter_arg(NULL); + ip6_filter_mask(ip_mask); + ip6_filter(ip6_filter_with_wolfsentry); } #endif /* LWIP_IPV6 */ @@ -1229,30 +1357,28 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_icmp_callback packet_filter_event_mask_t icmp_mask) { #if LWIP_ICMP || LWIP_ICMP6 + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); - if (icmp_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); - WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - } + if (icmp_mask) + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ICMP); + else + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ICMP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); #endif #if LWIP_ICMP if (icmp_mask) { - icmp_filter(icmp4_filter_with_wolfsentry); - icmp_filter_mask(icmp_mask); icmp_filter_arg((void *)wolfsentry); - } else - icmp_filter(NULL); + icmp_filter_mask(icmp_mask); + icmp_filter(icmp4_filter_with_wolfsentry); + } #endif /* LWIP_ICMP */ #if LWIP_ICMP6 if (icmp_mask) { - icmp6_filter(icmp6_filter_with_wolfsentry); - icmp6_filter_mask(icmp_mask); icmp6_filter_arg((void *)wolfsentry); - } else { - icmp6_filter(NULL); - icmp6_filter_mask(0); - icmp6_filter_arg(NULL); + icmp6_filter_mask(icmp_mask); + icmp6_filter(icmp6_filter_with_wolfsentry); } #endif /* LWIP_ICMP6 */ @@ -1271,22 +1397,23 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_tcp_callback( packet_filter_event_mask_t tcp_mask) { #if LWIP_TCP + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); if (tcp_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_TCP); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - tcp_filter(tcp_filter_with_wolfsentry); /* make sure wolfSentry sees the close/reset events that balance earlier * accepts, for concurrent-connection accounting purposes. */ if (tcp_mask & (FILT_MASK(ACCEPTING) | FILT_MASK(CLOSED) | FILT_MASK(REMOTE_RESET))) tcp_mask |= FILT_MASK(ACCEPTING) | FILT_MASK(CLOSED) | FILT_MASK(REMOTE_RESET); - tcp_filter_mask(tcp_mask); tcp_filter_arg((void *)wolfsentry); + tcp_filter_mask(tcp_mask); + tcp_filter(tcp_filter_with_wolfsentry); } else { - tcp_filter(NULL); - tcp_filter_mask(0); - tcp_filter_arg(NULL); + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_TCP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } WOLFSENTRY_UNLOCK_AND_RETURN_OK; #else @@ -1302,17 +1429,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_udp_callback( packet_filter_event_mask_t udp_mask) { #if LWIP_UDP + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); if (udp_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_UDP); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - udp_filter(udp_filter_with_wolfsentry); - udp_filter_mask(udp_mask); udp_filter_arg((void *)wolfsentry); + udp_filter_mask(udp_mask); + udp_filter(udp_filter_with_wolfsentry); } else { - udp_filter(NULL); - udp_filter_mask(0); - udp_filter_arg(NULL); + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_UDP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } WOLFSENTRY_UNLOCK_AND_RETURN_OK; #else diff --git a/src/routes.c b/src/routes.c index c9a13acb..9ccaffed 100644 --- a/src/routes.c +++ b/src/routes.c @@ -248,17 +248,16 @@ static inline int cmp_addrs_bitmaskful( if (left_addr_len < right_addr_len) { maskful_addr = right_addr; - mask_len = right_addr_len >> 1; + mask_len = WOLFSENTRY_BITS_TO_BYTES(right_addr_len >> 1); target_addr = left_addr; - padding_offset = mask_len - left_addr_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(left_addr_len); } else { maskful_addr = left_addr; - mask_len = left_addr_len >> 1; + mask_len = WOLFSENTRY_BITS_TO_BYTES(left_addr_len >> 1); target_addr = right_addr; - padding_offset = mask_len - right_addr_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(right_addr_len); } - mask_len = WOLFSENTRY_BITS_TO_BYTES(mask_len); mask = maskful_addr + mask_len; for (i = 0; i < mask_len; ++i) { @@ -296,28 +295,21 @@ static inline int addr_bitmask_match_size( const byte *b, unsigned int b_len) { - const byte *longer_addr, *shorter_addr; - unsigned int mask_len, i, padding_offset; + const byte *mask; + unsigned int mask_len, i; int ret = 0; + /* specificity is the number of bits the longer, maskful address's mask constrains */ if (a_len < b_len) { - longer_addr = b; mask_len = WOLFSENTRY_BITS_TO_BYTES(b_len >> 1); - shorter_addr = a; - padding_offset = mask_len - a_len; + mask = b + mask_len; } else { - longer_addr = a; mask_len = WOLFSENTRY_BITS_TO_BYTES(a_len >> 1); - shorter_addr = b; - padding_offset = mask_len - b_len; + mask = a + mask_len; } - for (ret = 0, i = 0; i < mask_len; ++i) { - if (i < padding_offset) - ret += popcount32(~(uint32_t)(longer_addr[i])) - 24; - else - ret += popcount32(~(uint32_t)(longer_addr[i] ^ shorter_addr[i - padding_offset])) - 24; - } + for (i = 0; i < mask_len; ++i) + ret += popcount32((uint32_t)mask[i]); return ret; } @@ -1004,6 +996,10 @@ static wolfsentry_errcode_t wolfsentry_route_new( } else { if (parent_event != NULL) { WOLFSENTRY_REFCOUNT_INCREMENT(parent_event->header.refcount, ret); + if (ret < 0) { + wolfsentry_route_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT, config->config.route_private_data_alignment, *new); + *new = NULL; + } } } @@ -1063,6 +1059,10 @@ static wolfsentry_errcode_t wolfsentry_route_new_by_exports( } else { if (parent_event != NULL) { WOLFSENTRY_REFCOUNT_INCREMENT(parent_event->header.refcount, ret); + if (ret < 0) { + wolfsentry_route_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT, config->config.route_private_data_alignment, *new); + *new = NULL; + } } } @@ -1165,8 +1165,9 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_clone( /* now zero the private data section. */ memset((*new_route)->data, 0, config->config.route_private_data_size); - /* finally, copy the address section, carefully. */ - memcpy((byte *)(*new_route)->data + (*new_route)->data_addr_offset, src_route->data, src_route->data_addr_size); + /* finally, copy the address section, carefully, reading it from the + * source's own address offset rather than the start of its data. */ + memcpy((byte *)(*new_route)->data + (*new_route)->data_addr_offset, (const byte *)src_route->data + src_route->data_addr_offset, src_route->data_addr_size); } WOLFSENTRY_TABLE_ENT_HEADER_RESET(**new_ent); @@ -1179,6 +1180,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_clone( thread, #endif config->config.route_private_data_alignment, *new_route); + *new_route = NULL; WOLFSENTRY_ERROR_RERETURN(ret); } WOLFSENTRY_REFCOUNT_INCREMENT((*new_route)->parent_event->header.refcount, ret); @@ -1320,13 +1322,28 @@ static wolfsentry_errcode_t wolfsentry_route_insert_1( wolfsentry_route_flags_t flags_before, flags_after; WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &route_to_insert->header)); wolfsentry_route_update_flags_1(route_to_insert, WOLFSENTRY_ROUTE_FLAG_NONE, WOLFSENTRY_ROUTE_FLAG_IN_TABLE, &flags_before, &flags_after); +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + if (route_to_insert->flags & (WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK | WOLFSENTRY_ROUTE_FLAG_LOCAL_ADDR_BITMASK)) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_bitmask_matching_downref(route_to_insert->sa_family, route_table)); +#endif + WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_INSERTED); + /* the caller drops the last reference on failure, so the route + * must not be linked into the purge list or wildcard bookkeeping. + */ + WOLFSENTRY_ERROR_RERETURN(ret); } - } else { - if (route_to_insert->parent_event) { - if (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) - WOLFSENTRY_SET_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); - } + } else ret = WOLFSENTRY_ERROR_ENCODE(OK); + + /* an aux event parents the ephemeral routes it generates, so it keeps its + * subevent role rather than taking on the mutually exclusive parent role. + * the owner count is authoritative, since a freed owner leaves the stored mark stale. + */ + if ((route_to_insert->parent_event != NULL) && + (WOLFSENTRY_ATOMIC_LOAD(route_to_insert->parent_event->aux_owner_count) == 0) && + (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT))) + { + WOLFSENTRY_SET_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); } if (route_to_insert->meta.purge_after) @@ -1472,6 +1489,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_into_table( wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -1496,6 +1516,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_by_exports_into_tabl wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (route_exports->parent_event_label) { @@ -1566,6 +1589,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_into_table_and_check wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -1647,6 +1673,7 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( int contiguous_search; wolfsentry_route_flags_t inexact_matches_buf; int seen_target_af; + int af_wildcard_span_entered; #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING int af_is_bitmask_matched; #endif @@ -1740,9 +1767,16 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( */ if (cursor_position == 0) { struct wolfsentry_event *parent_event = ((struct wolfsentry_route *)cursor.point)->parent_event ? ((struct wolfsentry_route *)cursor.point)->parent_event : NULL; - if ((action_results == NULL) || (parent_event == NULL) || (parent_event->config == NULL) || - (((*action_results & parent_event->config->config.action_res_filter_bits_set) == parent_event->config->config.action_res_filter_bits_set) && - ((~(*action_results) & parent_event->config->config.action_res_filter_bits_unset) == parent_event->config->config.action_res_filter_bits_unset))) + /* PENALTYBOXED is mutable and not part of the key, so an exact hit + * must still honor the bind/listen exclusion applied by the full scan. + */ + int excluded_reject_route = (action_results != NULL) && + WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES) && + WOLFSENTRY_MASKIN_BITS(((struct wolfsentry_route *)cursor.point)->flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); + if ((! excluded_reject_route) && + ((action_results == NULL) || (parent_event == NULL) || (parent_event->config == NULL) || + (((*action_results & parent_event->config->config.action_res_filter_bits_set) == parent_event->config->config.action_res_filter_bits_set) && + ((~(*action_results) & parent_event->config->config.action_res_filter_bits_unset) == parent_event->config->config.action_res_filter_bits_unset)))) { int effective_priority = parent_event ? parent_event->priority : 0; if (effective_priority <= table->highest_priority_route_in_table) { @@ -1803,6 +1837,7 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( #endif seen_target_af = 0; + af_wildcard_span_entered = 0; for (i = (struct wolfsentry_route *)wolfsentry_table_cursor_current(&cursor); i; @@ -1812,9 +1847,15 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( if (! seen_target_af) seen_target_af = 1; } else { + /* jump to the wildcard-AF span at most once; the span's own + * members never match the target AF and would otherwise re-trigger + * the jump forever. + */ if (seen_target_af && + (! af_wildcard_span_entered) && (! (target_route->flags & WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD))) { + af_wildcard_span_entered = 1; if (table->last_af_wildcard_route) { i = table->last_af_wildcard_route; wolfsentry_table_cursor_set(&cursor, &i->header); @@ -1996,6 +2037,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_default_policy_set( if ((default_policy != WOLFSENTRY_ACTION_RES_NONE) && (! WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT))) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (WOLFSENTRY_CHECK_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT)) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + /* a port reset only qualifies a reject verdict. */ + if (WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_PORT_RESET) && + (! WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_REJECT))) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_MUTEX_OR_RETURN(); table->default_policy = default_policy; if (table == wolfsentry->routes) @@ -2280,6 +2327,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_from_table( wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if ((action_results == NULL) || (n_deleted == NULL)) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -2332,6 +2382,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_by_id( struct wolfsentry_event *trigger_event = NULL; struct wolfsentry_route *route; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (trigger_label) { @@ -2380,6 +2433,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( struct wolfsentry_eventconfig_internal *config; wolfsentry_route_flags_t current_rule_route_flags; wolfsentry_errcode_t ret; + wolfsentry_errcode_t action_ret; wolfsentry_time_t now; int penalty_triggered = 0; wolfsentry_hitcount_t derog_snap; @@ -2431,10 +2485,11 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( #endif { rule_route->meta.purge_after = new_purge_after; /* coherent assignment */ - if (route_table->purge_list.tail != &rule_route->purge_links) { - wolfsentry_list_ent_delete(&route_table->purge_list, &rule_route->purge_links); - wolfsentry_route_purge_list_insert(route_table, rule_route); - } + /* the refreshed route may now be the newest rather than the + * stalest, so always reposition it instead of leaving a + * just-refreshed tail stranded at the stale end. */ + wolfsentry_list_ent_delete(&route_table->purge_list, &rule_route->purge_links); + wolfsentry_route_purge_list_insert(route_table, rule_route); } } } @@ -2447,7 +2502,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( * trigger_event->action_list must call wolfsentry_route_set_wildcard(), * in addition to setting _ACTION_RES_INSERT. */ - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, trigger_event, @@ -2456,7 +2511,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } @@ -2474,10 +2532,15 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( wolfsentry_route_increment_hitcount(WOLFSENTRY_CONTEXT_ARGS_OUT, rule_route, action_results); } parent_event = route_table->default_event; + /* the rule route and its event changed, so refresh the cached config + * and flags to match the fallthrough route rather than reusing the + * superseded route's state. */ + config = (parent_event && parent_event->config) ? parent_event->config : &wolfsentry->config; + current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->match_action_list.header) > 0)) { - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2486,7 +2549,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } @@ -2505,29 +2571,40 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( (void)ret; } - if ((! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) && - (config->config.max_connection_count > 0)) - { + if (! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) { if (*action_results & WOLFSENTRY_ACTION_RES_CONNECT) { - if (rule_route->meta.connection_count >= config->config.max_connection_count) { + uint16_t new_connection_count; + /* count every connection so disconnects stay balanced when the + * limit changes; a zero limit only means nothing is rejected. */ + if ((config->config.max_connection_count > 0) && + (rule_route->meta.connection_count >= config->config.max_connection_count)) + { *action_results |= WOLFSENTRY_ACTION_RES_REJECT; ret = WOLFSENTRY_ERROR_ENCODE(OK); goto done; } - if (WOLFSENTRY_ATOMIC_INCREMENT_BY_ONE(rule_route->meta.connection_count) > config->config.max_connection_count) { - WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(rule_route->meta.connection_count, new_connection_count); + /* with no limit a saturated counter simply stops counting; under a + * limit it cannot track another connection, so refuse it. */ + if ((config->config.max_connection_count > 0) && + ((new_connection_count == 0) || (new_connection_count > config->config.max_connection_count))) + { + if (new_connection_count != 0) + WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); *action_results |= WOLFSENTRY_ACTION_RES_REJECT; ret = WOLFSENTRY_ERROR_ENCODE(OK); goto done; } } else if (*action_results & WOLFSENTRY_ACTION_RES_DISCONNECT) { uint16_t new_connection_count; - /* important to decrement safely, because untimely route - * deletions can lead to a decrement being assigned to a - * different route than the one originally incremented. - */ + /* decrement safely: an untimely route deletion can assign a + * decrement to a different route than the one incremented. */ WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(rule_route->meta.connection_count, 1, new_connection_count); - if (new_connection_count == MAX_UINT_OF(rule_route->meta.connection_count)) + /* only an already-zero count under an enforced limit is + * anomalous; with no limit, unconnected routes legitimately see + * disconnects, so do not warn on them. */ + if ((new_connection_count == MAX_UINT_OF(rule_route->meta.connection_count)) && + (config->config.max_connection_count > 0)) WOLFSENTRY_WARN("_RES_DISCONNECT for route #" WOLFSENTRY_ENT_ID_FMT ", whose connection_count is already zero.", rule_route->header.id); } } @@ -2638,7 +2715,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( parent_event && (wolfsentry_list_ent_get_len(&parent_event->update_action_list.header) > 0)) { WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2647,13 +2724,16 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; /* no need to refresh current_rule_route_flags */ } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->decision_action_list.header) > 0)) { WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2662,7 +2742,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; /* no need to refresh current_rule_route_flags */ } @@ -2695,6 +2778,8 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( struct wolfsentry_route *rule_route = NULL; struct wolfsentry_event *trigger_event = NULL; wolfsentry_errcode_t ret; + int apply_default_policy = 0; + static int warned_unset_default_policy = 0; if ((remote == NULL) || (local == NULL) || @@ -2767,6 +2852,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( else { /* nothing to do. */ /* carry through ret from final wolfsentry_route_lookup_0(), and impose the default policy. */ + apply_default_policy = 1; goto just_free_resources; } @@ -2806,8 +2892,29 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, trigger_event, NULL /* action_results */)); if (rule_route == NULL) { + if (! apply_default_policy) { + /* a resource failure (not a clean no-match) prevented rule + * evaluation; fail closed with an explicit reject rather than + * silently applying the default policy as a successful fallthrough, + * so an integrator that gates on _RES_REJECT drops the packet. + */ + *action_results |= WOLFSENTRY_ACTION_RES_ERROR | WOLFSENTRY_ACTION_RES_REJECT; + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } if (inexact_matches) *inexact_matches = WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD; + if (route_table->default_policy == WOLFSENTRY_ACTION_RES_NONE) { + /* claim the one-shot atomically so only a single caller warns. */ + int warn_expected = 0; +#ifdef WOLFSENTRY_THREADSAFE + if (WOLFSENTRY_ATOMIC_CMPXCHG(&warned_unset_default_policy, &warn_expected, 1, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) +#else + warn_expected = warned_unset_default_policy; + warned_unset_default_policy = 1; + if (! warn_expected) +#endif + WOLFSENTRY_WARN("%s", "default route policy is unset (NONE); unmatched traffic falls through with no accept or reject verdict.\n"); + } *action_results |= route_table->default_policy | WOLFSENTRY_ACTION_RES_FALLTHROUGH; WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN(USED_FALLBACK); } @@ -2832,6 +2939,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route_table, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2849,6 +2958,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2857,6 +2968,8 @@ static wolfsentry_errcode_t check_user_inited_result(wolfsentry_action_res_t act if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | + WOLFSENTRY_ACTION_RES_FALLTHROUGH | + WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_DEALLOCATED | WOLFSENTRY_ACTION_RES_ERROR)) @@ -2879,7 +2992,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table_w wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route_table, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2897,7 +3013,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_inited_ wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2953,6 +3072,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_id( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, id, event_label, event_label_len, caller_arg, action_results)); } @@ -2966,7 +3087,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_id_with_i wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, id, event_label, event_label_len, caller_arg, action_results)); } @@ -3019,6 +3143,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_route_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route, event_label, event_label_len, caller_arg, action_results)); } @@ -3032,7 +3158,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_route_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route, event_label, event_label_len, caller_arg, action_results)); } @@ -3042,6 +3171,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit WOLFSENTRY_LOCAL_VOID wolfsentry_route_purge_list_insert(struct wolfsentry_route_table *route_table, struct wolfsentry_route *route_to_insert) { struct wolfsentry_list_ent_header *point_ent; + /* deletion leaves an entry's own links stale, and a head insert keeps prev */ + route_to_insert->purge_links.prev = NULL; + route_to_insert->purge_links.next = NULL; for (wolfsentry_list_ent_get_first(&route_table->purge_list, &point_ent); point_ent; point_ent = point_ent->next) { const struct wolfsentry_route *point_route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(point_ent); if (point_route->meta.purge_after < route_to_insert->meta.purge_after) @@ -3057,6 +3189,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_purge_time_set( wolfsentry_time_t purge_after) { WOLFSENTRY_HAVE_MUTEX_OR_RETURN(); + if (route->header.parent_table == NULL) + WOLFSENTRY_ERROR_RETURN(NOT_PERMITTED); if (route->meta.purge_after) wolfsentry_list_ent_delete(&((struct wolfsentry_route_table *)route->header.parent_table)->purge_list, &route->purge_links); route->meta.purge_after = purge_after; @@ -3075,6 +3209,9 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( wolfsentry_errcode_t ret; int n = 0; wolfsentry_action_res_t fallback_action_results = 0; + struct wolfsentry_list_ent_header *cursor; + int examined = 0; + wolfsentry_time_t requeue_delay = 0; #ifdef WOLFSENTRY_THREADSAFE int have_mutex = 0; int got_lock = 0; @@ -3092,6 +3229,11 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( WOLFSENTRY_ERROR_RETURN(ALREADY); } + if (mode == 2) { + if ((ret = WOLFSENTRY_INTERVAL_FROM_SECONDS(WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS, 0 /* howlong_nsecs */, &requeue_delay)) < 0) + WOLFSENTRY_ERROR_RERETURN(ret); + } + if (action_results == NULL) action_results = &fallback_action_results; @@ -3121,17 +3263,39 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( table = wolfsentry->routes; while (table->purge_list.tail) { - struct wolfsentry_route *route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(table->purge_list.tail); - if (mode != 3) { - if (route->meta.purge_after > now) - break; - if ((route->meta.connection_count > 0) && - (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && - ((table->max_purgeable_idle_time == 0) || (now - route->meta.last_hit_time > table->max_purgeable_idle_time))) - { + struct wolfsentry_route *route = NULL; + int found = 0; + int requeue = 0; + wolfsentry_time_t idle = 0; + /* re-scan from the most stale entry each pass, because a delete runs + * application callbacks that can mutate the purge list; skip routes + * that still have a live connection within the idle limit so one + * protected route does not strand the expired routes behind it. */ + for (cursor = table->purge_list.tail; cursor != NULL; cursor = cursor->prev) { + /* the per-dispatch purge runs on the packet path, so bound its scan */ + if ((mode == 2) && (++examined > 8)) break; + route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(cursor); + if (mode != 3) { + if (route->meta.purge_after > now) + break; + /* a route never hit since insertion has been idle since its insertion */ + idle = WOLFSENTRY_DIFF_TIME(now, (route->meta.last_hit_time > route->meta.insert_time) ? route->meta.last_hit_time : route->meta.insert_time); + if ((route->meta.connection_count > 0) && + (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && + ((table->max_purgeable_idle_time <= 0) || (idle <= table->max_purgeable_idle_time))) + { + if (mode != 2) + continue; + /* requeue it so later bounded scans reach the routes behind it */ + requeue = 1; + } } + found = 1; + break; } + if (! found) + break; #ifdef WOLFSENTRY_THREADSAFE if (! have_mutex) { if (mode == 2) @@ -3146,6 +3310,19 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( have_mutex = 1; } #endif + if (requeue) { + wolfsentry_time_t delay = requeue_delay; + /* not before the margin, unless the idle limit expires sooner */ + if ((table->max_purgeable_idle_time > 0) && (idle >= 0) && + (table->max_purgeable_idle_time - idle < requeue_delay)) + { + delay = table->max_purgeable_idle_time - idle + 1; + } + wolfsentry_list_ent_delete(&table->purge_list, &route->purge_links); + route->meta.purge_after = WOLFSENTRY_ADD_TIME(now, delay); + wolfsentry_route_purge_list_insert(table, route); + continue; + } ret = wolfsentry_route_delete_0(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, table, NULL /* trigger_event */, route, action_results, 0 /* defer_p */); if (ret < 0) { #ifdef WOLFSENTRY_THREADSAFE @@ -3395,10 +3572,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_increment_derogatory_count( WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, (unsigned)count_to_add, new_derogatory_count); if (new_derogatory_count == 0) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); - } else { - WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, (unsigned)(-count_to_add), new_derogatory_count); + } else if (count_to_add < 0) { + /* negate in the unsigned domain so INT_MIN does not overflow. */ + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, 0U - (unsigned)count_to_add, new_derogatory_count); if (new_derogatory_count == MAX_UINT_OF(route->meta.derogatory_count)) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + } else { + new_derogatory_count = WOLFSENTRY_ATOMIC_LOAD(route->meta.derogatory_count); } if (new_derogatory_count_ptr) @@ -3421,10 +3601,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_increment_commendable_count WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, (unsigned)count_to_add, new_commendable_count); if (new_commendable_count == 0) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); - } else { - WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, (unsigned)(-count_to_add), new_commendable_count); + } else if (count_to_add < 0) { + /* negate in the unsigned domain so INT_MIN does not overflow. */ + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, 0U - (unsigned)count_to_add, new_commendable_count); if (new_commendable_count == MAX_UINT_OF(route->meta.commendable_count)) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + } else { + new_commendable_count = WOLFSENTRY_ATOMIC_LOAD(route->meta.commendable_count); } if (new_commendable_count_ptr) @@ -3483,11 +3666,24 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_set_wildcard( } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD; - route->remote.addr_len = 0; + if (route->remote.addr_len > 0) { + /* the local address is stored immediately after the remote address, + * so dropping the remote address requires sliding the local bytes + * down to keep WOLFSENTRY_ROUTE_LOCAL_ADDR() valid. */ + if (route->local.addr_len > 0) + memmove(WOLFSENTRY_ROUTE_REMOTE_ADDR(route), + WOLFSENTRY_ROUTE_LOCAL_ADDR(route), + WOLFSENTRY_ROUTE_LOCAL_ADDR_BYTES(route)); + route->remote.addr_len = 0; + route->data_addr_size = (uint16_t)WOLFSENTRY_ROUTE_LOCAL_ADDR_BYTES(route); + } } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD; - route->local.addr_len = 0; + if (route->local.addr_len > 0) { + route->local.addr_len = 0; + route->data_addr_size = (uint16_t)WOLFSENTRY_ROUTE_REMOTE_ADDR_BYTES(route); + } } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD; @@ -3585,6 +3781,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_get_default_event( if (table->default_event->label_len >= *event_label_len) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(BUFFER_TOO_SMALL); memcpy(event_label, table->default_event->label, (size_t)(table->default_event->label_len + 1)); + *event_label_len = table->default_event->label_len; WOLFSENTRY_UNLOCK_AND_RETURN_OK; } @@ -3926,9 +4123,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_address( if (sa_family == WOLFSENTRY_AF_LINK || sa_family == WOLFSENTRY_AF_LINK64) { unsigned int i; - if ((addr_bits >> 3) * 3 > (size_t)*buflen) + unsigned int addr_bytes = (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits); + if (addr_bytes * 3 > (size_t)*buflen) WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); - for (i=0; i < (addr_bits >> 3); ++i) { + for (i=0; i < addr_bytes; ++i) { if (i > 0) *buf++ = ':'; *buf++ = hexdigit_ntoa(addr[i] >> 4); @@ -3955,7 +4153,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_address( *buf++ = '0'; *buf++ = 'x'; for (i=0; i < 4; ++i) { - if (i < (addr_bits >> 3)) { + if (i < (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits)) { *buf++ = hexdigit_ntoa(addr[i] >> 4); *buf++ = hexdigit_ntoa(addr[i]); } else { @@ -4011,8 +4209,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_flag_assoc_by_name(const ch if (len < 0) len = (int)strlen(name); for (i = wolfsentry_route_flag_names; i < end_ptr_of_array(wolfsentry_route_flag_names); ++i) { - if ((strncmp(i->name, name, (size_t)len) == 0) && - (i->name[len] == 0)) + /* compare lengths first so a shared NUL does not lead to reading + * i->name[len] past the end of the name literal. */ + if ((strlen(i->name) == (size_t)len) && + (strncmp(i->name, name, (size_t)len) == 0)) { *flag = i->flag; WOLFSENTRY_RETURN_OK; @@ -4074,6 +4274,30 @@ static wolfsentry_errcode_t ws_itoa(int i, unsigned char **out, size_t *spc) { #define unwrite_byte() do { --(*json_out); ++(*json_out_len); } while (0) #define unwrite_bytes(l) do { size_t _l = (size_t)(l); (*json_out) -= _l; (*json_out_len) += _l; } while (0) +/* labels and names are arbitrary bytes, so they must be escaped to keep the + * output parseable and to keep them from injecting JSON structure. + */ +static wolfsentry_errcode_t write_json_string(unsigned char **json_out, size_t *json_out_len, const char *s, size_t s_len) { + static const char hexdigits[] = "0123456789abcdef"; + size_t i; + + write_byte('"'); + for (i = 0; i < s_len; ++i) { + unsigned char c = (unsigned char)s[i]; + if ((c == '"') || (c == '\\')) { + write_byte('\\'); + write_byte(c); + } else if (c < 0x20U) { + write_string("\\u00"); + write_byte((unsigned char)hexdigits[c >> 4U]); + write_byte((unsigned char)hexdigits[c & 0xfU]); + } else + write_byte(c); + } + write_byte('"'); + WOLFSENTRY_RETURN_OK; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_route *r, @@ -4085,9 +4309,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( write_byte('{'); if (r->parent_event) { - write_string("\"parent-event\":\""); - write_bytes(r->parent_event->label, r->parent_event->label_len); - write_string("\","); + write_string("\"parent-event\":"); + WOLFSENTRY_RERETURN_IF_ERROR(write_json_string(json_out, json_out_len, r->parent_event->label, (size_t)r->parent_event->label_len)); + write_byte(','); } { const struct wolfsentry_route_flag_name_map_ent *i; @@ -4119,10 +4343,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( if (family_name_len + 2 > *json_out_len) ret = WOLFSENTRY_ERROR_ENCODE(BUFFER_TOO_SMALL); else { - write_byte('"'); - write_bytes(family_name, family_name_len); - write_byte('"'); - rendered_family = 1; + ret = write_json_string(json_out, json_out_len, family_name, family_name_len); + if (WOLFSENTRY_IS_SUCCESS(ret)) + rendered_family = 1; } if (addr_family) { wolfsentry_errcode_t drop_ret = wolfsentry_addr_family_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, addr_family, NULL /* action_results */ ); @@ -4151,18 +4374,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( (r->sa_family == WOLFSENTRY_AF_INET6))) { char get_buf[256]; - struct protoent protoent, *p; + struct protoent protoent, *p = NULL; /* note this is glibc-specific and non-standard; other libc * implementations have same name but different args. + * an unknown number returns 0 with a null result pointer. */ - if (getprotobynumber_r(r->sa_proto, - &protoent, - get_buf, sizeof get_buf, - &p) == 0) + if ((getprotobynumber_r(r->sa_proto, + &protoent, + get_buf, sizeof get_buf, + &p) == 0) && + (p != NULL)) { - write_byte('"'); - write_string(protoent.p_name); - write_byte('"'); + WOLFSENTRY_RERETURN_IF_ERROR(write_json_string(json_out, json_out_len, p->p_name, strlen(p->p_name))); rendered_proto = 1; } } @@ -4183,8 +4406,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( if (have_r_attr(SA_REMOTE_ADDR)) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING if (r->flags & WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK) { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4211,8 +4435,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( } else #endif { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4251,8 +4476,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( if (have_r_attr(SA_LOCAL_ADDR)) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING if (r->flags & WOLFSENTRY_ROUTE_FLAG_LOCAL_ADDR_BITMASK) { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4279,8 +4505,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( } else #endif { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4455,7 +4682,7 @@ static wolfsentry_errcode_t wolfsentry_route_render_address(WOLFSENTRY_CONTEXT_A if (sa_family == WOLFSENTRY_AF_LINK || sa_family == WOLFSENTRY_AF_LINK64) { unsigned int i; - for (i=0; i < (addr_bits >> 3); ++i) { + for (i=0; i < (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits); ++i) { if (fprintf(f, "%s%02x", i ? ":" : "", (unsigned int)addr[i]) < 0) WOLFSENTRY_ERROR_RETURN(IO_FAILED); } @@ -4813,6 +5040,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_table_clone_header( ((struct wolfsentry_route_table *)dest_table)->max_purgeable_routes = ((struct wolfsentry_route_table *)src_table)->max_purgeable_routes; + ((struct wolfsentry_route_table *)dest_table)->max_purgeable_idle_time = + ((struct wolfsentry_route_table *)src_table)->max_purgeable_idle_time; ((struct wolfsentry_route_table *)dest_table)->default_policy = ((struct wolfsentry_route_table *)src_table)->default_policy; @@ -4837,14 +5066,17 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_table_clone_header( } if (((struct wolfsentry_route_table *)src_table)->fallthrough_route != NULL) { + /* take the source reference before releasing the destination's own + * route, so a failed increment leaves the destination route intact + * rather than a freed, still-published pointer. */ + WOLFSENTRY_REFCOUNT_INCREMENT(((struct wolfsentry_route_table *)src_table)->fallthrough_route->header.refcount, ret); + WOLFSENTRY_RERETURN_IF_ERROR(ret); if (((struct wolfsentry_route_table *)dest_table)->fallthrough_route != NULL) { WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_drop_reference_1( WOLFSENTRY_CONTEXT_ARGS_OUT_EX(dest_context), ((struct wolfsentry_route_table *)dest_table)->fallthrough_route, NULL /* action_results */)); } ((struct wolfsentry_route_table *)dest_table)->fallthrough_route = ((struct wolfsentry_route_table *)src_table)->fallthrough_route; - WOLFSENTRY_REFCOUNT_INCREMENT(((struct wolfsentry_route_table *)dest_table)->fallthrough_route->header.refcount, ret); - WOLFSENTRY_RERETURN_IF_ERROR(ret); } if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)) @@ -4890,8 +5122,17 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_copy_metadata( /* pointers are advanced inside the loop. */) { if (from_i->header.id == to_i->header.id) { + /* meta carries purge_after, which determines purge_list membership + * and ordering; refresh the list around the copy so it stays + * consistent with the new value. + */ + if (to_i->meta.purge_after) + wolfsentry_list_ent_delete(&to_table->purge_list, &to_i->purge_links); to_i->flags = from_i->flags; to_i->meta = from_i->meta; + to_i->header.hitcount = from_i->header.hitcount; + if (to_i->meta.purge_after) + wolfsentry_route_purge_list_insert(to_table, to_i); } else { int cmpret = wolfsentry_route_key_cmp_1(from_i, to_i, 0 /* match_wildcards_p */, NULL /* inexact_matches */); if (cmpret < 0) { diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index d3411ab6..c2860f3a 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -53,7 +53,13 @@ static int wolfip_cleanup_registered; static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_event *event); -static wolfsentry_errcode_t wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_IN); +static wolfsentry_errcode_t wolfip_apply_masks( + WOLFSENTRY_CONTEXT_ARGS_IN, + uint32_t new_mask_eth, + uint32_t new_mask_ip, + uint32_t new_mask_icmp, + uint32_t new_mask_tcp, + uint32_t new_mask_udp); static byte wolfip_if_idx_to_byte(unsigned int if_idx) { @@ -163,6 +169,10 @@ static int wolfip_dispatch_event( if (WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE) < 0) return -WOLFIP_EACCES; + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + return -WOLFIP_EACCES; + if (wolfip_action_rejects(*action_results)) return -WOLFIP_EACCES; @@ -209,7 +219,9 @@ static int wolfip_filter_ethernet( action_results = WOLFSENTRY_ACTION_RES_SOCK_ERROR; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_link_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -261,7 +273,9 @@ static int wolfip_filter_ipv4( action_results = WOLFSENTRY_ACTION_RES_SOCK_ERROR; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -312,6 +326,7 @@ static int wolfip_filter_tcp( case WOLFIP_FILT_CLOSE_WAIT: route_flags |= outbound ? WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT : WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + action_results = WOLFSENTRY_ACTION_RES_CLOSE_WAIT; break; case WOLFIP_FILT_PORT_UNREACHABLE: route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; @@ -357,10 +372,12 @@ static int wolfip_filter_tcp( break; case WOLFIP_FILT_DISSOCIATE: route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; - action_results = WOLFSENTRY_ACTION_RES_DEROGATORY; + action_results = WOLFSENTRY_ACTION_RES_CLOSED; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -441,8 +458,14 @@ static int wolfip_filter_udp( action_results = WOLFSENTRY_ACTION_RES_STOPPED_LISTENING | WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES; break; + case WOLFIP_FILT_DISSOCIATE: + route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; + action_results = WOLFSENTRY_ACTION_RES_CLOSED; + break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -503,8 +526,25 @@ static int wolfip_filter_icmp( route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; action_results = WOLFSENTRY_ACTION_RES_SOCK_ERROR; break; + case WOLFIP_FILT_BINDING: + route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD; + action_results = WOLFSENTRY_ACTION_RES_BINDING | + WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES; + /* socket lifecycle events carry the local endpoint in src_ip; orient + * the sockaddrs as locally originated so the bound address lands in + * local, while route_flags keeps the inbound binding semantics. */ + outbound = 1; + break; + case WOLFIP_FILT_DISSOCIATE: + route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; + action_results = WOLFSENTRY_ACTION_RES_CLOSED; + break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -518,8 +558,10 @@ static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_e { struct wolfsentry_context *wolfsentry = (struct wolfsentry_context *)arg; - if ((wolfsentry == NULL) || (event == NULL)) + if (event == NULL) return 0; + if (wolfsentry == NULL) + return -WOLFIP_EACCES; switch (event->meta.ip_proto) { case WOLFIP_FILTER_PROTO_ETH: @@ -533,30 +575,55 @@ static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_e case WOLFIP_FILTER_PROTO_ICMP: return wolfip_filter_icmp(wolfsentry, event); default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped protocol %d\n", + (int)event->meta.ip_proto); + return -WOLFIP_EACCES; } } -static wolfsentry_errcode_t wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_IN) +static wolfsentry_errcode_t wolfip_apply_masks( + WOLFSENTRY_CONTEXT_ARGS_IN, + uint32_t new_mask_eth, + uint32_t new_mask_ip, + uint32_t new_mask_icmp, + uint32_t new_mask_tcp, + uint32_t new_mask_udp) { - uint32_t combined = wolfip_mask_eth | wolfip_mask_ip | wolfip_mask_icmp | - wolfip_mask_tcp | wolfip_mask_udp; + uint32_t combined = new_mask_eth | new_mask_ip | new_mask_icmp | + new_mask_tcp | new_mask_udp; wolfsentry_errcode_t ret = WOLFSENTRY_ERROR_ENCODE(OK); + /* validate ownership before mutating any shared state, so a rejected call + * cannot perturb the owning context's masks or callback (fail-open). the + * check applies on the disable path too. */ + if ((wolfip_filter_context != NULL) && (wolfip_filter_context != wolfsentry)) + return WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); + if (combined) { - if (wolfip_filter_context && (wolfip_filter_context != wolfsentry)) - return WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); - if (!wolfip_cleanup_registered) { + if (! wolfip_cleanup_registered) { ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_wolfip_filter_callbacks, NULL); if (ret < 0) return ret; wolfip_cleanup_registered = 1; } + } + + wolfip_mask_eth = new_mask_eth; + wolfip_mask_ip = new_mask_ip; + wolfip_mask_icmp = new_mask_icmp; + wolfip_mask_tcp = new_mask_tcp; + wolfip_mask_udp = new_mask_udp; + + if (combined) { wolfip_filter_context = wolfsentry; wolfIP_filter_set_callback(wolfip_filter_with_wolfsentry, wolfsentry); } else { wolfIP_filter_set_callback(NULL, NULL); wolfip_filter_context = NULL; + /* release ownership of the cleanup registration so the next context to + * install receives its own cleanup hook rather than relying on a stale + * flag bound to a context that may since be torn down. */ + wolfip_cleanup_registered = 0; } wolfIP_filter_set_eth_mask(wolfip_mask_eth); @@ -570,9 +637,13 @@ static wolfsentry_errcode_t wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_IN) WOLFSENTRY_API_VOID wolfsentry_cleanup_wolfip_filter_callbacks(WOLFSENTRY_CONTEXT_ARGS_IN, void *cleanup_arg) { - (void)wolfsentry; WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; (void)cleanup_arg; + /* a cleanup hook fires when its registering context is destroyed; only tear + * down the shared filter state if that context still owns it, so destroying + * a non-owner cannot silently disable a surviving owner's filtering. */ + if ((wolfip_filter_context != NULL) && (wolfip_filter_context != wolfsentry)) + return; wolfip_mask_eth = wolfip_mask_ip = wolfip_mask_icmp = wolfip_mask_tcp = wolfip_mask_udp = 0; wolfip_cleanup_registered = 0; wolfIP_filter_set_callback(NULL, NULL); @@ -592,8 +663,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_ethernet_ca WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_eth = ethernet_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + ethernet_mask, wolfip_mask_ip, wolfip_mask_icmp, wolfip_mask_tcp, + wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -605,8 +677,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_ip4_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_ip = ip_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, ip_mask, wolfip_mask_icmp, wolfip_mask_tcp, + wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -618,11 +691,23 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_icmp_callba WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_icmp = icmp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, wolfip_mask_ip, icmp_mask, wolfip_mask_tcp, + wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } +static uint32_t wolfip_normalize_tcp_mask(uint32_t tcp_mask) +{ + /* an accept is counted as a connection, so when accepts are requested also + * deliver the close and reset events that release it; teardown-only masks + * must not turn on accept counting. */ + if (tcp_mask & WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING)) + tcp_mask |= WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | + WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET); + return tcp_mask; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_tcp_callback( WOLFSENTRY_CONTEXT_ARGS_IN, uint32_t tcp_mask) @@ -631,14 +716,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_tcp_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - if (tcp_mask & (WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | - WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | - WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET))) - tcp_mask |= WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | - WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | - WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET); - wolfip_mask_tcp = tcp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, wolfip_mask_ip, wolfip_mask_icmp, + wolfip_normalize_tcp_mask(tcp_mask), wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -650,8 +730,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_udp_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_udp = udp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, wolfip_mask_ip, wolfip_mask_icmp, wolfip_mask_tcp, + udp_mask); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -667,12 +748,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_callbacks( WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_eth = ethernet_mask; - wolfip_mask_ip = ip_mask; - wolfip_mask_icmp = icmp_mask; - wolfip_mask_tcp = tcp_mask; - wolfip_mask_udp = udp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + ethernet_mask, ip_mask, icmp_mask, wolfip_normalize_tcp_mask(tcp_mask), + udp_mask); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } diff --git a/src/wolfsentry_internal.c b/src/wolfsentry_internal.c index 33f6e221..2555f4d0 100644 --- a/src/wolfsentry_internal.c +++ b/src/wolfsentry_internal.c @@ -275,6 +275,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_coupled_table_clone( goto out; new1->parent_table = dest_table1; new2->parent_table = dest_table2; + new1->prev = prev; if (prev) prev->next = new1; else @@ -317,6 +318,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_id_allocate( ) { wolfsentry_errcode_t ret; + wolfsentry_ent_id_t first_candidate = WOLFSENTRY_ENT_ID_NONE; WOLFSENTRY_HAVE_MUTEX_OR_RETURN(); @@ -326,7 +328,20 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_id_allocate( if (ret < 0) break; } else { - ent->id = ++wolfsentry->mk_id_cb_state.id_counter; + ++wolfsentry->mk_id_cb_state.id_counter; + /* the counter wraps past the reserved null ID; collisions with + * live IDs are handled by the ITEM_ALREADY_PRESENT retry. + */ + if (wolfsentry->mk_id_cb_state.id_counter == WOLFSENTRY_ENT_ID_NONE) + ++wolfsentry->mk_id_cb_state.id_counter; + if (first_candidate == WOLFSENTRY_ENT_ID_NONE) + first_candidate = wolfsentry->mk_id_cb_state.id_counter; + else if (wolfsentry->mk_id_cb_state.id_counter == first_candidate) { + ent->id = WOLFSENTRY_ENT_ID_NONE; + ret = WOLFSENTRY_ERROR_ENCODE(SYS_RESOURCE_FAILED); + break; + } + ent->id = wolfsentry->mk_id_cb_state.id_counter; } ret = wolfsentry_table_ent_insert_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, ent); @@ -429,8 +444,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_table_ent_delete_by_id_1(WOLFSE --wolfsentry->ents_by_id.n_ents; ++wolfsentry->ents_by_id.n_deletes; - /* if possible, reclaim the ID */ - if ((! wolfsentry->mk_id_cb) && (ent->id == (wolfsentry->mk_id_cb_state.id_counter - 1))) + /* if possible, reclaim the ID (the counter equals the newest issued ID). */ + if ((! wolfsentry->mk_id_cb) && (ent->id == wolfsentry->mk_id_cb_state.id_counter)) --wolfsentry->mk_id_cb_state.id_counter; ent->id = WOLFSENTRY_ENT_ID_NONE; @@ -555,7 +570,10 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_table_free_ents(WOLFSENTRY_CONT * e.g., a coupled wolfsentry_addr_family_byname is freed when the * corresponding wolfsentry_addr_family_bynumber is freed. */ - table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, i, NULL /* action_results */); + ret = table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, i, NULL /* action_results */); + /* teardown must run to completion and release its lock, so report a + * failed destructor and keep freeing instead of abandoning the table. */ + WOLFSENTRY_WARN_ON_FAILURE(ret); i = next; } WOLFSENTRY_RETURN_OK; diff --git a/src/wolfsentry_internal.h b/src/wolfsentry_internal.h index 484c5911..c13556a4 100644 --- a/src/wolfsentry_internal.h +++ b/src/wolfsentry_internal.h @@ -340,6 +340,7 @@ struct wolfsentry_event { * policy for the ephemeral route. */ + wolfsentry_refcount_t aux_owner_count; /* events whose aux_event is this one */ byte label_len; char label[WOLFSENTRY_FLEXIBLE_ARRAY_SIZE]; @@ -494,6 +495,7 @@ struct wolfsentry_context { #endif struct wolfsentry_table_header ents_by_id; struct wolfsentry_list_header cleanup_hooks; + int action_dispatch_depth; /* nonzero while an action dispatch loop is active, so list mutators refuse to free a live entry */ }; #ifdef WOLFSENTRY_THREADSAFE diff --git a/src/wolfsentry_ll.h b/src/wolfsentry_ll.h index 59ec7514..bc7bb82b 100644 --- a/src/wolfsentry_ll.h +++ b/src/wolfsentry_ll.h @@ -71,8 +71,10 @@ static inline void wolfsentry_list_ent_insert_before(struct wolfsentry_list_head if (point_ent->prev) { new_ent->prev = point_ent->prev; new_ent->prev->next = new_ent; - } else + } else { + new_ent->prev = NULL; list->head = new_ent; + } point_ent->prev = new_ent; ++list->len; } @@ -89,8 +91,10 @@ static inline void wolfsentry_list_ent_insert_after(struct wolfsentry_list_heade if (point_ent->next) { new_ent->next = point_ent->next; new_ent->next->prev = new_ent; - } else + } else { + new_ent->next = NULL; list->tail = new_ent; + } point_ent->next = new_ent; ++list->len; } diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index fc590a01..152c121b 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -85,7 +85,7 @@ WOLFSENTRY_API const char *wolfsentry_errcode_source_string(wolfsentry_errcode_t } static const char *user_defined_errors[WOLFSENTRY_ERROR_ID_MAX - WOLFSENTRY_ERROR_ID_USER_BASE + 1] = {0}; -static const char *user_defined_successes[WOLFSENTRY_ERROR_ID_MAX + WOLFSENTRY_SUCCESS_ID_USER_BASE + 1] = {0}; +static const char *user_defined_successes[WOLFSENTRY_ERROR_ID_MAX - WOLFSENTRY_SUCCESS_ID_USER_BASE + 1] = {0}; WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_error_string_set(enum wolfsentry_error_id wolfsentry_error_id, const char *message_string) { if (wolfsentry_error_id < 0) { @@ -586,15 +586,23 @@ static void *wolfsentry_builtin_memalign( WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; if (alignment && size) { - uint32_t hdr_size = sizeof(uint16_t) + (alignment - 1); - void *p = pvPortMalloc(size + hdr_size); - if (p) { + size_t hdr_size = sizeof(uint16_t) + (alignment - 1); + /* require power-of-two alignment, a base offset that fits the stored + * uint16_t, and no overflow when reserving the header. + */ + if (((alignment & (alignment - 1)) == 0) && + (hdr_size <= (size_t)MAX_UINT_OF(uint16_t)) && + (size <= (size_t)MAX_UINT_OF(size_t) - hdr_size)) + { + void *p = pvPortMalloc(size + hdr_size); + if (p) { #ifdef WOLFSENTRY_MALLOC_DEBUG - WOLFSENTRY_ATOMIC_INCREMENT(n_mallocs, 1); + WOLFSENTRY_ATOMIC_INCREMENT(n_mallocs, 1); #endif - /* Align to powers of two */ - ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); - *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); + /* Align to powers of two */ + ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); + *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); + } } } WOLFSENTRY_RETURN_VALUE(ptr); @@ -668,7 +676,10 @@ static void *wolfsentry_builtin_realloc( void *ret = realloc(ptr, size); if ((ptr == NULL) && (ret != NULL)) WOLFSENTRY_ATOMIC_INCREMENT(n_mallocs, 1); - else if ((ptr != NULL) && (ret == NULL)) + /* a failed nonzero-size realloc leaves the original block live, so + * only a zero-size request counts as a deallocation. + */ + else if ((ptr != NULL) && (ret == NULL) && (size == 0)) WOLFSENTRY_ATOMIC_DECREMENT(n_mallocs, 1); return ret; } @@ -687,14 +698,22 @@ static void *wolfsentry_builtin_memalign( WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; if (alignment && size) { size_t hdr_size = sizeof(uint16_t) + (alignment - 1); - void *p = malloc(size + hdr_size); - if (p) { - /* Align to powers of two */ - ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); - *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); - } - /* cppcheck-suppress memleak + /* require power-of-two alignment, a base offset that fits the stored + * uint16_t, and no overflow when reserving the header. */ + if (((alignment & (alignment - 1)) == 0) && + (hdr_size <= (size_t)MAX_UINT_OF(uint16_t)) && + (size <= (size_t)MAX_UINT_OF(size_t) - hdr_size)) + { + void *p = malloc(size + hdr_size); + if (p) { + /* Align to powers of two */ + ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); + *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); + } + /* cppcheck-suppress memleak + */ + } } #ifdef WOLFSENTRY_MALLOC_DEBUG if (ptr != NULL) @@ -912,31 +931,34 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_free_thread_context(struct wolfse WOLFSENTRY_RETURN_OK; } -static wolfsentry_time_t usecs_to_wolfsentry_time(struct wolfsentry_context *wolfsentry, long usecs) { - wolfsentry_time_t wolfsentry_time; - wolfsentry_errcode_t ret = WOLFSENTRY_INTERVAL_FROM_SECONDS(0, usecs * 1000L, &wolfsentry_time); - if (ret < 0) { - WOLFSENTRY_WARN_ON_FAILURE(ret); - return 0; - } - else - return wolfsentry_time; +static wolfsentry_errcode_t usecs_to_wolfsentry_time(struct wolfsentry_context *wolfsentry, long usecs, wolfsentry_time_t *wolfsentry_time) { + /* split into seconds and nanoseconds so the full long range of usecs is + * preserved without overflowing the intermediate multiplication. + */ + WOLFSENTRY_RERETURN_IF_ERROR(WOLFSENTRY_INTERVAL_FROM_SECONDS(usecs / 1000000L, (usecs % 1000000L) * 1000L, wolfsentry_time)); + WOLFSENTRY_RETURN_OK; } -static long wolfsentry_time_to_usecs(struct wolfsentry_context *wolfsentry, wolfsentry_time_t ts) { +static wolfsentry_errcode_t wolfsentry_time_to_usecs(struct wolfsentry_context *wolfsentry, wolfsentry_time_t ts, long *usecs) { time_t howlong_secs; long howlong_nsecs; - wolfsentry_errcode_t ret = WOLFSENTRY_INTERVAL_TO_SECONDS(ts, &howlong_secs, &howlong_nsecs); - if (ret < 0) { - WOLFSENTRY_WARN_ON_FAILURE(ret); - return 0; - } - else if (howlong_secs * (time_t)1000000 >= (time_t)MAX_SINT_OF(long) - (time_t)1000000L) - return (long)MAX_SINT_OF(long); - else if (howlong_secs * (time_t)1000000 <= (time_t)MIN_SINT_OF(long)) - return (long)MIN_SINT_OF(long); + long howlong_usecs; + WOLFSENTRY_RERETURN_IF_ERROR(WOLFSENTRY_INTERVAL_TO_SECONDS(ts, &howlong_secs, &howlong_nsecs)); + howlong_usecs = howlong_nsecs / 1000L; + /* compare the seconds quotient and then the remainder so a representable + * value is never saturated, and the multiply never overflows long. + */ + if ((howlong_secs > (time_t)(MAX_SINT_OF(long) / (int64_t)1000000)) || + ((howlong_secs == (time_t)(MAX_SINT_OF(long) / (int64_t)1000000)) && + (howlong_usecs > (long)(MAX_SINT_OF(long) % (int64_t)1000000)))) + *usecs = (long)MAX_SINT_OF(long); + else if ((howlong_secs < (time_t)(MIN_SINT_OF(long) / (int64_t)1000000)) || + ((howlong_secs == (time_t)(MIN_SINT_OF(long) / (int64_t)1000000)) && + (howlong_usecs < (long)(MIN_SINT_OF(long) % (int64_t)1000000)))) + *usecs = (long)MIN_SINT_OF(long); else - return ((long)howlong_secs * 1000000L) + (howlong_nsecs / 1000L); + *usecs = ((long)howlong_secs * 1000000L) + howlong_usecs; + WOLFSENTRY_RETURN_OK; } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_set_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_IN, wolfsentry_time_t rel_when) { @@ -963,7 +985,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_set_deadline_rel(WOLFSENTRY_CONTE } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_set_deadline_rel_usecs(WOLFSENTRY_CONTEXT_ARGS_IN, long usecs) { - return wolfsentry_set_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_OUT, usecs_to_wolfsentry_time(wolfsentry, usecs)); + wolfsentry_time_t rel_when; + wolfsentry_errcode_t ret = usecs_to_wolfsentry_time(wolfsentry, usecs, &rel_when); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + return wolfsentry_set_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_OUT, rel_when); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_get_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_IN, wolfsentry_time_t *rel_when) { @@ -1004,8 +1029,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_get_deadline_rel_usecs(WOLFSENTRY { return ret; } - if (usecs) - *usecs = wolfsentry_time_to_usecs(wolfsentry, rel_when); + if (usecs) { + wolfsentry_errcode_t conv_ret = wolfsentry_time_to_usecs(wolfsentry, rel_when, usecs); + WOLFSENTRY_RERETURN_IF_ERROR(conv_ret); + } return ret; } @@ -1538,16 +1565,47 @@ static int freertos_sem_destroy( sem_t * sem ) } WOLFSENTRY_RETURN_VALUE(0); } + #ifndef EAGAIN + #define EAGAIN 11 /* Try again */ + #endif + #ifndef EINTR + #define EINTR 4 /* Interrupted system call */ + #endif + #define sem_timedwait threadx_sem_timedwait static int threadx_sem_timedwait( sem_t * sem, const struct timespec * abstime ) { - if (tx_semaphore_get(sem, (uint32_t)(TX_TICK_TIME_MS * - (abstime->tv_sec * 1000) + (abstime->tv_nsec / 1000000))) != TX_SUCCESS) { - errno = EINVAL; - WOLFSENTRY_RETURN_VALUE(-1); + ULONG wait_ticks = TX_WAIT_FOREVER; + UINT tx_ret; + + if (abstime != NULL) { + uint64_t deadline_ticks, now_ticks; + if ((abstime->tv_sec < 0) || (abstime->tv_nsec < 0) || (abstime->tv_nsec >= 1000000000L)) { + errno = EINVAL; + WOLFSENTRY_RETURN_VALUE(-1); + } + /* abstime is on the builtin get_time clock, so invert that tick mapping, rounding up so a sub-tick future deadline still waits a tick. */ + deadline_ticks = ((uint64_t)abstime->tv_sec * (uint64_t)TX_TICK_TIME_MS) + (((uint64_t)abstime->tv_nsec + (uint64_t)999999) / (uint64_t)1000000); + now_ticks = (uint64_t)tx_time_get(); + if (deadline_ticks <= now_ticks) + wait_ticks = TX_NO_WAIT; + else if (deadline_ticks - now_ticks >= (uint64_t)TX_WAIT_FOREVER) + wait_ticks = (ULONG)(TX_WAIT_FOREVER - 1); + else + wait_ticks = (ULONG)(deadline_ticks - now_ticks); } - WOLFSENTRY_RETURN_VALUE(0); + + tx_ret = tx_semaphore_get(sem, wait_ticks); + if (tx_ret == TX_SUCCESS) + WOLFSENTRY_RETURN_VALUE(0); + if (tx_ret == TX_NO_INSTANCE) + errno = ETIMEDOUT; + else if (tx_ret == TX_WAIT_ABORTED) + errno = EINTR; + else + errno = EINVAL; + WOLFSENTRY_RETURN_VALUE(-1); } #define sem_wait threadx_sem_wait static int threadx_sem_wait( sem_t * sem ) @@ -1561,11 +1619,16 @@ static int freertos_sem_destroy( sem_t * sem ) #define sem_trywait threadx_sem_trywait static int threadx_sem_trywait( sem_t * sem ) { - if (tx_semaphore_get(sem, TX_NO_WAIT) != TX_SUCCESS) { + UINT tx_ret = tx_semaphore_get(sem, TX_NO_WAIT); + if (tx_ret == TX_SUCCESS) + WOLFSENTRY_RETURN_VALUE(0); + if (tx_ret == TX_NO_INSTANCE) + errno = EAGAIN; + else if (tx_ret == TX_WAIT_ABORTED) + errno = EINTR; + else errno = EINVAL; - WOLFSENTRY_RETURN_VALUE(-1); - } - WOLFSENTRY_RETURN_VALUE(0); + WOLFSENTRY_RETURN_VALUE(-1); } static int threadx_sem_destroy( sem_t * sem ) { @@ -3018,9 +3081,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl WOLFSENTRY_THREAD_ASSERT_NULL_OR_INITED(thread); - /* unlocking a recursive mutex, like recursively locking one, can be done lock-free. */ + /* unlocking a recursive mutex, like recursively locking one, can be done + * lock-free, except when unwinding to the promotion depth, which must + * evaluate the downgrade logic below. + */ if ((WOLFSENTRY_ATOMIC_LOAD(lock->write_lock_holder) == WOLFSENTRY_THREAD_GET_ID) && - (lock->holder_count.write > 1)) + (lock->holder_count.write > 1) && + (lock->holder_count.write - 1 != lock->promoted_at_count)) { --lock->holder_count.write; if (thread) @@ -3127,16 +3194,25 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl if (lock->promoted_at_count == lock->holder_count.write) { lock->promoted_at_count = 0; if ((flags & WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE) && (! thread->tracked_shared_lock)) { - WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); + /* same bookkeeping as wolfsentry_lock_mutex2shared(): writer count becomes reader count. */ if (flags & (WOLFSENTRY_LOCK_FLAG_TRY_RESERVATION_TOO | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)) { + if (lock->read2write_reservation_holder != WOLFSENTRY_THREAD_NO_ID) /* can't happen, but be sure. */ { + ret = WOLFSENTRY_ERROR_ENCODE(INTERNAL_CHECK_FATAL); + goto out; + } + ++thread->mutex_and_reservation_count; WOLFSENTRY_ATOMIC_STORE(lock->read2write_reservation_holder, WOLFSENTRY_THREAD_GET_ID); + lock->read2write_waiter_read_count = lock->holder_count.write; /* note, not incrementing write_waiter_count, to allow shared lockers to get locks until the redemption phase. */ - lock->read2write_waiter_read_count = lock->holder_count.read; + } + WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); + WOLFSENTRY_ATOMIC_STORE(lock->write_lock_holder, WOLFSENTRY_THREAD_NO_ID); + thread->mutex_and_reservation_count -= lock->holder_count.write; + thread->shared_count += lock->holder_count.read; + thread->recursion_of_tracked_lock = lock->holder_count.read; + thread->tracked_shared_lock = lock; + if (flags & (WOLFSENTRY_LOCK_FLAG_TRY_RESERVATION_TOO | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)) { ++lock->holder_count.read; /* suppress posts to sem_read2write_waiters until wolfsentry_lock_shared2mutex_redeem() is entered. */ - thread->mutex_and_reservation_count -= lock->holder_count.write - 1; /* -1 for the reservation */ - thread->shared_count += lock->holder_count.read; - thread->tracked_shared_lock = lock; - thread->recursion_of_tracked_lock = lock->holder_count.read; ret = WOLFSENTRY_SUCCESS_ENCODE(LOCK_OK_AND_GOT_RESV); } else ret = WOLFSENTRY_ERROR_ENCODE(OK); @@ -3167,11 +3243,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl } } } else if (lock->read_waiter_count > 0) { - int i; - lock->holder_count.read += lock->read_waiter_count; + /* post once per queued reader, not per holder, as existing shared holders need no wakeup. */ + int read_waiter_count = lock->read_waiter_count; + lock->holder_count.read += read_waiter_count; lock->read_waiter_count = 0; WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); - for (i = 0; i < lock->holder_count.read; ++i) { + for (; read_waiter_count > 0; --read_waiter_count) { if (sem_post(&lock->sem_read_waiters) < 0) { ret = WOLFSENTRY_ERROR_ENCODE(SYS_OP_FATAL); goto out; @@ -3471,7 +3548,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_time_now_plus_delta(struct wolfse WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_time_to_timespec(struct wolfsentry_context *wolfsentry, wolfsentry_time_t t, struct timespec *ts) { time_t epoch_secs; long int epoch_nsecs; - WOLFSENTRY_TO_EPOCH_TIME(t, &epoch_secs, &epoch_nsecs); + wolfsentry_errcode_t ret = WOLFSENTRY_TO_EPOCH_TIME(t, &epoch_secs, &epoch_nsecs); + WOLFSENTRY_RERETURN_IF_ERROR(ret); ts->tv_sec = epoch_secs; ts->tv_nsec = epoch_nsecs; WOLFSENTRY_RETURN_OK; @@ -3483,7 +3561,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_time_now_plus_delta_timespec(stru long int epoch_nsecs; wolfsentry_errcode_t ret = WOLFSENTRY_GET_TIME(&now); WOLFSENTRY_RERETURN_IF_ERROR(ret); - WOLFSENTRY_TO_EPOCH_TIME(WOLFSENTRY_ADD_TIME(now, td), &epoch_secs, &epoch_nsecs); + ret = WOLFSENTRY_TO_EPOCH_TIME(WOLFSENTRY_ADD_TIME(now, td), &epoch_secs, &epoch_nsecs); + WOLFSENTRY_RERETURN_IF_ERROR(ret); ts->tv_sec = epoch_secs; ts->tv_nsec = epoch_nsecs; WOLFSENTRY_RETURN_OK; @@ -3571,9 +3650,22 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_object_checkout(WOLFSENTRY_CONTEX } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_IN, void *object, wolfsentry_action_res_t *action_results) { - if (object == NULL) + struct wolfsentry_table_ent_header *ent = (struct wolfsentry_table_ent_header *)object; + if (ent == NULL) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); - WOLFSENTRY_ERROR_RERETURN(((struct wolfsentry_table_ent_header *)object)->parent_table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_table_ent_header *)object, action_results)); + if (ent->parent_table == NULL) { + /* the fallthrough route is never linked into a table, but is documented as releasable here. */ + if ((wolfsentry->routes != NULL) && + (wolfsentry->routes->fallthrough_route != NULL) && + (object == (void *)wolfsentry->routes->fallthrough_route)) + { + WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->routes->fallthrough_route, action_results)); + } + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + } + if (ent->parent_table->free_fn == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_ERROR_RERETURN(ent->parent_table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, ent, action_results)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_eventconfig_init( @@ -3605,6 +3697,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_eventconfig_check( WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (config->derogatory_threshold_for_penaltybox > MAX_UINT_OF(instance_of_field(struct wolfsentry_route, meta.derogatory_count))) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (config->max_connection_count > MAX_UINT_OF(instance_of_field(struct wolfsentry_route, meta.connection_count))) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); ret = wolfsentry_route_check_flags_sensical(config->route_flags_to_add_on_insert); WOLFSENTRY_RERETURN_IF_ERROR(ret); @@ -3743,8 +3837,13 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( ) { #ifdef WOLFSENTRY_THREADSAFE - wolfsentry_errcode_t ret = wolfsentry_lock_have_mutex(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + wolfsentry_errcode_t ret; + /* a context whose lock never came up is still private to its allocator, so no mutex is possible or needed. */ + int lock_inited = (WOLFSENTRY_ATOMIC_LOAD((*wolfsentry)->lock.state) != WOLFSENTRY_LOCK_UNINITED); + if (lock_inited) { + ret = wolfsentry_lock_have_mutex(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } #endif if ((*wolfsentry)->routes != NULL) @@ -3763,10 +3862,12 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( #endif #ifdef WOLFSENTRY_THREADSAFE - ret = wolfsentry_lock_unlock(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - ret = wolfsentry_lock_destroy(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (lock_inited) { + ret = wolfsentry_lock_unlock(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + ret = wolfsentry_lock_destroy(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } #endif WOLFSENTRY_FREE_1((*wolfsentry)->hpi.allocator, *wolfsentry); @@ -3774,6 +3875,16 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( WOLFSENTRY_RETURN_OK; } +static void *wolfsentry_context_alloc_zeroed( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(const struct wolfsentry_host_platform_interface *hpi), + size_t size) +{ + void *ret = WOLFSENTRY_MALLOC_1(hpi->allocator, size); + if (ret != NULL) + memset(ret, 0, size); + WOLFSENTRY_RETURN_VALUE(ret); +} + static wolfsentry_errcode_t wolfsentry_context_init_1( struct wolfsentry_context *wolfsentry #ifdef WOLFSENTRY_THREADSAFE @@ -3817,36 +3928,29 @@ static wolfsentry_errcode_t wolfsentry_context_alloc_1( ) { wolfsentry_errcode_t ret; - if ((*wolfsentry = (struct wolfsentry_context *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof **wolfsentry)) == NULL) + if ((*wolfsentry = (struct wolfsentry_context *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof **wolfsentry)) == NULL) WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); - memset(*wolfsentry, 0, sizeof **wolfsentry); - (*wolfsentry)->hpi = *hpi; - if ((((*wolfsentry)->events = (struct wolfsentry_event_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->events)) == NULL) || - (((*wolfsentry)->actions = (struct wolfsentry_action_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->actions)) == NULL) || - (((*wolfsentry)->routes = (struct wolfsentry_route_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->routes)) == NULL) || - (((*wolfsentry)->user_values = (struct wolfsentry_kv_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->user_values)) == NULL) || - (((*wolfsentry)->addr_families_bynumber = (struct wolfsentry_addr_family_bynumber_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->addr_families_bynumber)) == NULL) + /* each table is zeroed as allocated, so a partial failure leaves only well-formed tables for free_1 to tear down. */ + if ((((*wolfsentry)->events = (struct wolfsentry_event_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->events)) == NULL) || + (((*wolfsentry)->actions = (struct wolfsentry_action_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->actions)) == NULL) || + (((*wolfsentry)->routes = (struct wolfsentry_route_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->routes)) == NULL) || + (((*wolfsentry)->user_values = (struct wolfsentry_kv_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->user_values)) == NULL) || + (((*wolfsentry)->addr_families_bynumber = (struct wolfsentry_addr_family_bynumber_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->addr_families_bynumber)) == NULL) #ifdef WOLFSENTRY_PROTOCOL_NAMES - || (((*wolfsentry)->addr_families_byname = (struct wolfsentry_addr_family_byname_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->addr_families_byname)) == NULL) + || (((*wolfsentry)->addr_families_byname = (struct wolfsentry_addr_family_byname_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->addr_families_byname)) == NULL) #endif ) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); } - memset((*wolfsentry)->events, 0, sizeof *(*wolfsentry)->events); - memset((*wolfsentry)->actions, 0, sizeof *(*wolfsentry)->actions); - memset((*wolfsentry)->routes, 0, sizeof *(*wolfsentry)->routes); - memset((*wolfsentry)->user_values, 0, sizeof *(*wolfsentry)->user_values); - memset((*wolfsentry)->addr_families_bynumber, 0, sizeof *(*wolfsentry)->addr_families_bynumber); #ifdef WOLFSENTRY_PROTOCOL_NAMES - memset((*wolfsentry)->addr_families_byname, 0, sizeof *(*wolfsentry)->addr_families_byname); if ((ret = wolfsentry_addr_family_table_pair(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(*wolfsentry), (*wolfsentry)->addr_families_bynumber, (*wolfsentry)->addr_families_byname)) < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_ERROR_RERETURN(ret); } #endif @@ -3856,7 +3960,12 @@ static wolfsentry_errcode_t wolfsentry_context_alloc_1( , lock_flags #endif )) < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); +#ifdef WOLFSENTRY_THREADSAFE + /* init_1 can fail after the lock is up, and free_1 then needs the mutex, which no one else can hold yet. */ + if (WOLFSENTRY_ATOMIC_LOAD((*wolfsentry)->lock.state) != WOLFSENTRY_LOCK_UNINITED) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_lock_mutex_abstimed(&(*wolfsentry)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE)); +#endif + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL /* thread */))); WOLFSENTRY_ERROR_RERETURN(ret); } @@ -3873,6 +3982,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( struct wolfsentry_host_platform_interface hpi; #ifdef WOLFSENTRY_THREADSAFE wolfsentry_lock_flags_t lock_flags = WOLFSENTRY_LOCK_FLAG_NONE; + wolfsentry_errcode_t cleanup_ret; #endif wolfsentry_errcode_t ret; @@ -3952,8 +4062,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( * because wolfsentry_interval_from_seconds() needs a valid * wolfsentry_context (circular dependency). fix it now that we can. */ - hpi.timecbs.interval_from_seconds((long int)(*wolfsentry)->config.config.penaltybox_duration, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.penaltybox_duration)); - hpi.timecbs.interval_from_seconds((long int)(*wolfsentry)->config.config.route_idle_time_for_purge, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.route_idle_time_for_purge)); + if ((ret = hpi.timecbs.interval_from_seconds((time_t)(*wolfsentry)->config.config.penaltybox_duration, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.penaltybox_duration))) < 0) + goto out; + if ((ret = hpi.timecbs.interval_from_seconds((time_t)(*wolfsentry)->config.config.route_idle_time_for_purge, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.route_idle_time_for_purge))) < 0) + goto out; (*wolfsentry)->config_at_creation = (*wolfsentry)->config; @@ -3968,7 +4080,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( out: if (ret < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); +#ifdef WOLFSENTRY_THREADSAFE + /* the new context is still private, so tear it down with no thread context, which a read-only thread or an expired deadline cannot block. */ + cleanup_ret = wolfsentry_lock_mutex_abstimed(&(*wolfsentry)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE); + if (cleanup_ret >= 0) + cleanup_ret = wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL /* thread */)); + WOLFSENTRY_WARN_ON_FAILURE(cleanup_ret); +#else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); +#endif } WOLFSENTRY_ERROR_RERETURN(ret); } @@ -4147,6 +4267,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( wolfsentry_clone_flags_t flags) { wolfsentry_errcode_t ret; +#ifdef WOLFSENTRY_THREADSAFE + wolfsentry_errcode_t cleanup_ret; +#endif #ifdef WOLFSENTRY_THREADSAFE WOLFSENTRY_SHARED_OR_RETURN(); @@ -4154,8 +4277,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( if ((ret = wolfsentry_context_alloc_1(&wolfsentry->hpi, thread, clone, wolfsentry->lock.flags)) < 0) WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - if ((ret = wolfsentry_context_lock_mutex_abstimed(*clone, thread, NULL)) < 0) - goto out; + if ((ret = wolfsentry_context_lock_mutex_abstimed(*clone, thread, NULL)) < 0) { + /* the clone is still private, so tear it down with no thread context, which a read-only thread or an expired deadline cannot block. */ + cleanup_ret = wolfsentry_lock_mutex_abstimed(&(*clone)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE); + if (cleanup_ret >= 0) + cleanup_ret = wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(clone, NULL /* thread */)); + WOLFSENTRY_WARN_ON_FAILURE(cleanup_ret); + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } #else if ((ret = wolfsentry_context_alloc_1(&wolfsentry->hpi, clone)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); @@ -4202,7 +4331,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( #endif if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)) { - ret = WOLFSENTRY_ERROR_ENCODE(OK); + /* the fresh route table needs its own fallthrough route, as wolfsentry_init_ex() provides. */ + ret = wolfsentry_route_table_fallthrough_route_alloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(*clone), (*clone)->routes); goto out; } @@ -4249,6 +4379,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_exchange(WOLFSENTRY_CONTE } #endif + /* refuse to swap tables out from under an action dispatch in progress on + * either context, which would strand the live list's reentrancy guard. + */ + if ((wolfsentry->action_dispatch_depth != 0) || (wolfsentry2->action_dispatch_depth != 0)) { + ret = WOLFSENTRY_ERROR_ENCODE(BUSY); + goto out; + } + /* now that we have a mutex on both contexts, coherently copy the route * metadata from the current context to the new one to be swapped in. */ @@ -4289,6 +4427,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_exchange(WOLFSENTRY_CONTE wolfsentry2->ents_by_id = scratch.ents_by_id; + /* keep the lock-free copy of the default policy in step with the swapped route tables. */ + WOLFSENTRY_ATOMIC_STORE(wolfsentry->default_policy, wolfsentry->routes->default_policy); + WOLFSENTRY_ATOMIC_STORE(wolfsentry2->default_policy, wolfsentry2->routes->default_policy); + /* note that wolfsentry->lock and wolfsentry->cleanup_hooks are not copied. * both of these follow the context pointer itself, which is necessarily * invariant once allocated by wolfsentry_init(), until final deallocation @@ -4358,13 +4500,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_base64_decode(const char *src, si else WOLFSENTRY_ERROR_RETURN(INVALID_ARG); } else if (this_char == (uint32_t)'@') { - if ((src + 1 < src_end) && (base64_inv_lut[*((unsigned char *)src + 1)] != '@')) - WOLFSENTRY_ERROR_RETURN(INVALID_ARG); ++pad_chars; decoded <<= 6U; continue; } + /* data must not appear after padding, in strict or lenient mode */ + if (pad_chars != 0) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + decoded <<= 6U; decoded |= this_char; decoded_bits += 6U; @@ -4391,12 +4535,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_base64_decode(const char *src, si case 0: break; case 1: + if (decoded_bits != 18U) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); *dest++ = (byte)(decoded >> 16U); *dest++ = (byte)(decoded >> 8U); dest_len += 2; break; - /* fall through */ case 2: + if (decoded_bits != 12U) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); *dest++ = (byte)(decoded >> 16U); ++dest_len; break; diff --git a/tests/filter-clang-analyzer.py b/tests/filter-clang-analyzer.py index f4e199fa..72bdbea1 100644 --- a/tests/filter-clang-analyzer.py +++ b/tests/filter-clang-analyzer.py @@ -8,9 +8,9 @@ ALLOWED_RESULTS = { - ("core.uninitialized.Assign", "src/json/centijson_value.c", 1285), - ("core.NullDereference", "src/json/centijson_value.c", 1483), - ("core.NullDereference", "src/json/centijson_value.c", 1520), + ("core.uninitialized.Assign", "src/json/centijson_value.c", 1366), + ("core.NullDereference", "src/json/centijson_value.c", 1564), + ("core.NullDereference", "src/json/centijson_value.c", 1601), } diff --git a/tests/unittests.c b/tests/unittests.c index 7a756860..824840b1 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -33,6 +33,11 @@ #define PRIVATE_DATA_ALIGNMENT 16 #endif +#ifdef __GNUC__ +/* the list tests call the inline list primitives directly from run-once code */ +#pragma GCC diagnostic ignored "-Winline" +#endif + #include "src/wolfsentry_internal.h" #if (defined(_POSIX_C_SOURCE) || defined(__MACH__)) && !defined(FREERTOS) @@ -272,6 +277,139 @@ static wolfsentry_errcode_t test_init(void) { WOLFSENTRY_ERROR_RERETURN(ret); } +static wolfsentry_errcode_t test_base64_decode_lenient_padding(void) { + /* A correctly padded value followed by ignorable whitespace must be + * accepted when lenient parsing is requested. + */ + static const char input[] = "QQ==\n"; + byte outbuf[8]; + size_t outbuf_spc = sizeof outbuf; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_base64_decode(input, strlen(input), outbuf, &outbuf_spc, 1 /* ignore_junk_p */)); + WOLFSENTRY_EXIT_ON_FALSE((outbuf_spc == 1) && (outbuf[0] == 'A')); + + /* lenient parsing still rejects data appearing after padding */ + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("Q=Q=", 4, outbuf, &outbuf_spc, 1 /* ignore_junk_p */)); + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("QQ==QUJD", 8, outbuf, &outbuf_spc, 1 /* ignore_junk_p */)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_base64_decode_quantum(void) { + /* A padded quantum must carry enough data sextets for its pad count; a + * single data sextet followed by padding is structurally invalid. + */ + byte outbuf[8]; + size_t outbuf_spc; + + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("A=", 2, outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("A==", 3, outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + + /* A well-formed padded quantum is still accepted. */ + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_base64_decode("QQ==", 4, outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + WOLFSENTRY_EXIT_ON_FALSE((outbuf_spc == 1) && (outbuf[0] == 'A')); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_eventconfig_overlap_bits(void) { + /* A bit cannot be simultaneously requested for addition and clearing. */ + struct wolfsentry_eventconfig config; + + memset(&config, 0, sizeof config); + config.route_flags_to_add_on_insert = WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + config.route_flags_to_clear_on_insert = WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + memset(&config, 0, sizeof config); + config.action_res_filter_bits_set = WOLFSENTRY_ACTION_RES_CONNECT; + config.action_res_filter_bits_unset = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + memset(&config, 0, sizeof config); + config.action_res_bits_to_add = WOLFSENTRY_ACTION_RES_CONNECT; + config.action_res_bits_to_clear = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_eventconfig_alignment(void) { + struct wolfsentry_eventconfig config; + + /* Non-zero alignment with zero private-data size is contradictory. */ + memset(&config, 0, sizeof config); + config.route_private_data_alignment = sizeof(void *); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* Alignment smaller than a pointer is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = 32; + config.route_private_data_alignment = 1; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* Non-power-of-two alignment is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = 64; + config.route_private_data_alignment = (3 * sizeof(void *)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* Alignment larger than the private-data size is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = sizeof(void *); + config.route_private_data_alignment = (2 * sizeof(void *)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* A private-data size beyond the data_addr_offset field is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = (size_t)0x10000; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_eventconfig_check(&config)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_eventconfig_derogatory_threshold(void) { + /* derogatory_threshold_for_penaltybox must fit the uint16_t per-route + * derogatory_count counter. + */ + struct wolfsentry_eventconfig config; + + memset(&config, 0, sizeof config); + /* the route's derogatory_count counter is 16-bit; only test the + * out-of-range rejection when the configured field can exceed that. + */ + if (MAX_UINT_OF(config.derogatory_threshold_for_penaltybox) > (uint64_t)0xffffU) { + config.derogatory_threshold_for_penaltybox = (wolfsentry_hitcount_t)0x10000U; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + } + + config.derogatory_threshold_for_penaltybox = (wolfsentry_hitcount_t)0xffffU; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_check(&config)); + + WOLFSENTRY_RETURN_OK; +} + +#ifdef WOLFSENTRY_ERROR_STRINGS +static wolfsentry_errcode_t test_user_string_table_bounds(void) { + /* Guard that the user-defined success/error string tables are sized to + * hold their highest valid index. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_error_string_set((enum wolfsentry_error_id)WOLFSENTRY_ERROR_ID_MAX, "boundary success string")); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_ALREADY_PRESENT, wolfsentry_user_error_string_set((enum wolfsentry_error_id)WOLFSENTRY_ERROR_ID_MAX, "boundary success string")); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_error_string_set((enum wolfsentry_error_id)(-WOLFSENTRY_ERROR_ID_MAX), "boundary error string")); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_ALREADY_PRESENT, wolfsentry_user_error_string_set((enum wolfsentry_error_id)(-WOLFSENTRY_ERROR_ID_MAX), "boundary error string")); + + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ERROR_STRINGS */ + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -1387,132 +1525,208 @@ TEST_SKIP(test_rw_locks) #endif /* WOLFSENTRY_THREADSAFE */ -#endif /* TEST_RWLOCKS */ +#if defined(WOLFSENTRY_THREADSAFE) -#ifdef TEST_STATIC_ROUTES +static int test_rw_locks_auto_downgrade(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_rwlock *lock; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif -static wolfsentry_errcode_t replace_rule_transactionally( - WOLFSENTRY_CONTEXT_ARGS_IN, - const char *event_label, - int event_label_len, - const struct wolfsentry_sockaddr *del_remote, - const struct wolfsentry_sockaddr *del_local, - wolfsentry_route_flags_t del_flags, - const struct wolfsentry_sockaddr *ins_remote, - const struct wolfsentry_sockaddr *ins_local, - wolfsentry_route_flags_t ins_flags, - wolfsentry_ent_id_t *ins_id, - wolfsentry_action_res_t *action_results) -{ - wolfsentry_errcode_t ret; - struct wolfsentry_context *new_wolfsentry_ctx = NULL; - int n_deleted; + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_PROMOTABLE_OR_RETURN(); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_alloc(wolfsentry_get_hpi(wolfsentry), thread, &lock, WOLFSENTRY_LOCK_FLAG_NONE)); - ret = wolfsentry_context_clone( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &new_wolfsentry_ctx, - WOLFSENTRY_CLONE_FLAG_NONE); - if (ret < 0) - goto out; + /* promote through a redeemed reservation, which records the promotion depth. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); - ret = wolfsentry_route_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX(new_wolfsentry_ctx), - NULL /* caller_arg */, - del_remote, - del_local, - del_flags, - event_label, - event_label_len, - action_results, - &n_deleted); - if (ret < 0) - goto out; + /* unwinding to the promotion depth with AUTO_DOWNGRADE must leave a shared lock. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_READ_LOCK, wolfsentry_lock_have_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(LACKING_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(thread->shared_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(thread->mutex_and_reservation_count == 0); + WOLFSENTRY_EXIT_ON_FALSE(thread->tracked_shared_lock == lock); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_destroy(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); - ret = wolfsentry_route_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX(new_wolfsentry_ctx), - NULL /* caller_arg */, - ins_remote, - ins_local, - ins_flags, - event_label, - event_label_len, - ins_id, - action_results); - if (ret < 0) - goto out; + /* same again, but downgrade with a new reservation and redeem it. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_init(wolfsentry_get_hpi(wolfsentry), thread, lock, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_READ_LOCK, wolfsentry_lock_have_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_have_shared2mutex_reservation(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); - ret = wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, new_wolfsentry_ctx); + /* unwinding past the promotion depth without AUTO_DOWNGRADE stays exclusive. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); -out: + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_free(&lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - if (new_wolfsentry_ctx != NULL) { - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&new_wolfsentry_ctx))); - } + WOLFSENTRY_RETURN_OK; +} +static void *wr_timeout_routine(struct rwlock_args *args) { + WOLFSENTRY_THREAD_HEADER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_GET_ERROR); + INCREMENT_PHASE(args); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(TIMED_OUT, wolfsentry_lock_mutex_timed(args->lock, thread, args->max_wait, WOLFSENTRY_LOCK_FLAG_NONE)); + INCREMENT_PHASE(args); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + return 0; +} - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); +static int test_rw_locks_reader_wakeup(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_rwlock *lock; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + volatile int measured_sequence[8], measured_sequence_i = 0; + pthread_t reader_thread, writer_thread; + struct rwlock_args reader_args, writer_args; + int reader_phase; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + (void)alarm(2); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_alloc(wolfsentry_get_hpi(wolfsentry), thread, &lock, WOLFSENTRY_LOCK_FLAG_NONE)); + + memset(&reader_args, 0, sizeof reader_args); + reader_args.wolfsentry = wolfsentry; + reader_args.measured_sequence = measured_sequence; + reader_args.measured_sequence_i = &measured_sequence_i; + reader_args.lock = lock; + writer_args = reader_args; + reader_args.thread_id = 1; + reader_args.max_wait = -1; + writer_args.thread_id = 2; + writer_args.max_wait = MAX_WAIT; + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_init(&reader_args.thread_phase_lock, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_init(&writer_args.thread_phase_lock, NULL)); + + /* hold the lock shared twice, so the first unlock leaves a shared holder in place. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* a timed writer queues, which forces the subsequent reader to queue behind it. */ + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_create(&writer_thread, 0 /* attr */, (void *(*)(void *))wr_timeout_routine, (void *)&writer_args)); + WAIT_FOR_PHASE(writer_args, 1); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_kill(writer_thread, 0)); + usleep(10000); + + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_create(&reader_thread, 0 /* attr */, (void *(*)(void *))rd_routine, (void *)&reader_args)); + WAIT_FOR_PHASE(reader_args, 1); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_kill(reader_thread, 0)); + usleep(10000); + + /* the writer times out and dequeues itself, leaving the reader queued. */ + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_join(writer_thread, 0 /* retval */)); + + /* the reader must still be parked, not already holding the lock, else the scenario was not reached. */ + usleep(10000); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_lock(&reader_args.thread_phase_lock)); + reader_phase = reader_args.thread_phase; + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_unlock(&reader_args.thread_phase_lock)); + WOLFSENTRY_EXIT_ON_FALSE(reader_phase == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(measured_sequence_i) == 0); + + /* this unlock releases the queued reader, and must post exactly one wakeup. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_join(reader_thread, 0 /* retval */)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(measured_sequence_i) == 2); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* a surplus wakeup token is detected as an inconsistency at destroy time. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_free(&lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_destroy(&reader_args.thread_phase_lock)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_destroy(&writer_args.thread_phase_lock)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + + (void)alarm(0); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; } -static int test_static_routes(void) { +#else + +TEST_SKIP(test_rw_locks_auto_downgrade) +TEST_SKIP(test_rw_locks_reader_wakeup) + +#endif /* WOLFSENTRY_THREADSAFE */ +static int test_object_release_tableless(void) { struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + const struct wolfsentry_route *fallthrough_route; + struct wolfsentry_route *route_ref = NULL; wolfsentry_action_res_t action_results; - int n_deleted; - wolfsentry_ent_id_t id; - wolfsentry_route_flags_t inexact_matches; - + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; struct { struct wolfsentry_sockaddr sa; byte addr_buf[4]; - } remote, local, remote_wildcard, local_wildcard; - - struct wolfsentry_eventconfig config = { + } remote, local; #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS - .route_private_data_size = PRIVATE_DATA_SIZE, - .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, - .max_connection_count = 10, - .derogatory_threshold_for_penaltybox = 4, - .penaltybox_duration = 1, /* denominated in seconds when passing to wolfsentry_init(). */ - .route_idle_time_for_purge = 0, - .flags = WOLFSENTRY_EVENTCONFIG_FLAG_NONE + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - PRIVATE_DATA_SIZE, - PRIVATE_DATA_ALIGNMENT, - 10, - 4, - 1, /* denominated in seconds when passing to wolfsentry_init(). */ - 0, - WOLFSENTRY_EVENTCONFIG_FLAG_NONE, - 0, - 0, - 0, - 0, - 0, - 0 + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif - }; - wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS, flags_wildcard; + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - struct wolfsentry_route_table *main_routes; - struct wolfsentry_route *route_ref; - wolfsentry_ent_id_t route_id; - int prefixlen; - byte *private_data; - size_t private_data_size; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_init_ex( - wolfsentry_build_settings, - WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), - &config, - &wolfsentry, - WOLFSENTRY_INIT_FLAG_NONE)); + /* the fallthrough route is never linked into a table, but is documented as releasable. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(fallthrough_route) == WOLFSENTRY_OBJECT_TYPE_UNINITED); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT, (void *)fallthrough_route, NULL /* action_results */)); + /* a checked-out route that has since been deleted is table-less too, and must be rejected, not dereferenced. */ + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); remote.sa.sa_family = local.sa.sa_family = AF_INET; remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; remote.sa.sa_port = 12345; @@ -1522,372 +1736,784 @@ static int test_static_routes(void) { memcpy(remote.sa.addr,"\0\1\2\3",sizeof remote.addr_buf); memcpy(local.sa.addr,"\377\376\375\374",sizeof local.addr_buf); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_and_check_out(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label */, 0 /* event_label_len */, &route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_ROUTE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route_ref), 0 /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_UNINITED); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); +#if defined(WOLFSENTRY_THREADSAFE) - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_FALLTHROUGH)); +static int test_context_clone_readonly_thread(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + /* a read-only thread can clone, but cannot take the clone's mutex, so the clone must be torn down. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_set_thread_readonly(thread)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NOT_PERMITTED, wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(clone == NULL); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_set_thread_readwrite(thread)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_FALLTHROUGH)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} -#ifdef WOLFSENTRY_UNITTEST_BENCHMARKS - { - struct timespec start_at, end_at; - uint64_t start_at_cycles, end_at_cycles; - int i; - double ns_per_call, cycles_per_call; +#else - WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &start_at)); - start_at_cycles = get_intel_cycles(); - for (i=0; i<1000000; ++i) { - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); - } - end_at_cycles = get_intel_cycles(); - WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &end_at)); - ns_per_call = ((double)(end_at.tv_sec - start_at.tv_sec) * 1000000000.0 + (double)(end_at.tv_nsec - start_at.tv_nsec)) / (double)i; - cycles_per_call = (double)(end_at_cycles - start_at_cycles) / (double)i; - printf("benchmark wolfsentry_route_event_dispatch() with empty route table: %.2f ns/call %.2f cycles/call\n", ns_per_call, cycles_per_call); -#ifdef WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE - if (cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE) { - (void)fprintf(stderr, "benchmark wolfsentry_route_event_dispatch() with empty route table: measured %.2f cycles/call exceeds max %.2f\n", cycles_per_call, (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE); - WOLFSENTRY_EXIT_ON_TRUE(cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE); - } +TEST_SKIP(test_context_clone_readonly_thread) + +#endif /* WOLFSENTRY_THREADSAFE */ + +static int test_context_clone_fallthrough(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; + struct wolfsentry_route_table *clone_routes; + const struct wolfsentry_route *fallthrough_route; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif - } -#endif /* WOLFSENTRY_UNITTEST_BENCHMARKS */ + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - /* non-decisional default_policy values (STOP/ERROR alone) must be rejected. */ - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_STOP)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ERROR)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + /* a full-flush clone starts with an empty route table, which must still carry a fallthrough route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), &clone_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), clone_routes, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), (void *)fallthrough_route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); - memcpy(remote.sa.addr,"\4\5\6\7",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); +static wolfsentry_errcode_t failing_interval_from_seconds(time_t howlong_secs, long howlong_nsecs, wolfsentry_time_t *howlong) { + (void)howlong_secs; + (void)howlong_nsecs; + (void)howlong; + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); +} -#if 0 - puts("table after first 2 inserts:"); - for (struct wolfsentry_route *i = (struct wolfsentry_route *)wolfsentry->routes.header.head; - i; - i = (struct wolfsentry_route *)(i->header.next)) - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_render(i, stdout)); - putchar('\n'); +static int test_init_interval_conversion_failure(void) { + struct wolfsentry_context *wolfsentry, *wolfsentry2 = NULL; + struct wolfsentry_host_platform_interface hpi; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10, .penaltybox_duration = 1 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - - WOLFSENTRY_EXIT_ON_SUCCESS(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); - WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + /* borrow the working platform interface, then make the seconds conversion fail. */ + hpi = *wolfsentry_get_hpi(wolfsentry); + hpi.timecbs.interval_from_seconds = failing_interval_from_seconds; - WOLFSENTRY_EXIT_ON_SUCCESS(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + /* a rejected duration must fail initialization and leave no context behind. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&hpi), + &config, + &wolfsentry2, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry2 == NULL); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); - memcpy(remote.sa.addr,"\3\4\5\6",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); - memcpy(remote.sa.addr,"\2\3\4\5",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); +static int test_context_exchange_default_policy(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; + wolfsentry_action_res_t policy; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_SUCCESS(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_REJECT)); - flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + /* stage a new policy in a clone, then swap it in, as a config reload does. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, clone)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_get(WOLFSENTRY_CONTEXT_ARGS_OUT, &policy)); + WOLFSENTRY_EXIT_ON_FALSE(policy == WOLFSENTRY_ACTION_RES_ACCEPT); + /* the lock-free dispatch fallback reads the context-level copy, which must follow the swap. */ + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->default_policy == WOLFSENTRY_ACTION_RES_ACCEPT); + WOLFSENTRY_EXIT_ON_FALSE(clone->default_policy == WOLFSENTRY_ACTION_RES_REJECT); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &remote.sa, - &local.sa, - flags, - 0 /* event_label_len */, - 0 /* event_label */, - 1 /* exact_p */, - &inexact_matches, - &route_ref)); +#endif /* TEST_RWLOCKS */ - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); +#ifdef TEST_STATIC_ROUTES - WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_ROUTE); +static wolfsentry_errcode_t test_ll_insert_before_head_links(void) { + struct wolfsentry_list_header list; + struct wolfsentry_list_ent_header a, b, c, d; + const struct wolfsentry_list_ent_header *cursor; + wolfsentry_hitcount_t n; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_private_data( - WOLFSENTRY_CONTEXT_ARGS_OUT, - route_ref, - (void **)&private_data, - &private_data_size)); + WOLFSENTRY_LIST_HEADER_RESET(list); + wolfsentry_list_ent_append(&list, &a); + wolfsentry_list_ent_append(&list, &b); + wolfsentry_list_ent_append(&list, &c); - if (private_data_size < PRIVATE_DATA_SIZE) { - printf("private_data_size is " SIZET_FMT " but expected %d.\n",private_data_size,PRIVATE_DATA_SIZE); - WOLFSENTRY_ERROR_RETURN(NOT_OK); - } - if ((PRIVATE_DATA_ALIGNMENT > 0) && ((uintptr_t)private_data % (uintptr_t)PRIVATE_DATA_ALIGNMENT)) { - printf("private_data (%p) is not aligned to %d.\n", private_data, PRIVATE_DATA_ALIGNMENT); - WOLFSENTRY_ERROR_RETURN(NOT_OK); + /* unlink a middle entry and reinsert it before the head, as purge-list + * refresh does; the unlink leaves its old links, so the head insertion + * must clear prev. + */ + wolfsentry_list_ent_delete(&list, &b); + wolfsentry_list_ent_insert_before(&list, list.head, &b); + + if (list.head != &b) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.head->prev != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.tail->next != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + wolfsentry_list_ent_insert_before(&list, list.head, &d); + if ((list.head != &d) || (list.head->prev != NULL)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + n = 0; + for (cursor = list.head; cursor != NULL; cursor = cursor->next) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->next == NULL) && (cursor != list.tail)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); - { - byte *i; - const byte *i_end; - for (i = private_data, i_end = (private_data + private_data_size); i < i_end; ++i) - *i = 'x'; + n = 0; + for (cursor = list.tail; cursor != NULL; cursor = cursor->prev) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->prev == NULL) && (cursor != list.head)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); -#if 0 - puts("table after deleting 4.5.6.7 and inserting 3 more:"); - for (struct wolfsentry_route *i = (struct wolfsentry_route *)wolfsentry->routes.header.head; - i; - i = (struct wolfsentry_route *)(i->header.next)) - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_render(i, stdout)); -#endif + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, &action_results)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); +static wolfsentry_errcode_t replace_rule_transactionally( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *event_label, + int event_label_len, + const struct wolfsentry_sockaddr *del_remote, + const struct wolfsentry_sockaddr *del_local, + wolfsentry_route_flags_t del_flags, + const struct wolfsentry_sockaddr *ins_remote, + const struct wolfsentry_sockaddr *ins_local, + wolfsentry_route_flags_t ins_flags, + wolfsentry_ent_id_t *ins_id, + wolfsentry_action_res_t *action_results) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_context *new_wolfsentry_ctx = NULL; + int n_deleted; - /* now test basic eventless dispatch using exact-match ents in the static table. */ + WOLFSENTRY_PROMOTABLE_OR_RETURN(); - WOLFSENTRY_CLEAR_ALL_BITS(action_results); + ret = wolfsentry_context_clone( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &new_wolfsentry_ctx, + WOLFSENTRY_CLONE_FLAG_NONE); + if (ret < 0) + goto out; - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - memcpy(remote.sa.addr,"\3\4\5\6",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + ret = wolfsentry_route_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(new_wolfsentry_ctx), + NULL /* caller_arg */, + del_remote, + del_local, + del_flags, + event_label, + event_label_len, + action_results, + &n_deleted); + if (ret < 0) + goto out; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + ret = wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(new_wolfsentry_ctx), + NULL /* caller_arg */, + ins_remote, + ins_local, + ins_flags, + event_label, + event_label_len, + ins_id, + action_results); + if (ret < 0) + goto out; - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); + ret = wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, new_wolfsentry_ctx); - flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); +out: - WOLFSENTRY_EXIT_ON_FALSE( - WOLFSENTRY_SUCCESS_CODE_IS( - wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results), - USED_FALLBACK)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == (WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD)); + if (new_wolfsentry_ctx != NULL) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&new_wolfsentry_ctx))); + } - memcpy(remote.sa.addr,"\2\3\4\5",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); +} - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); +static wolfsentry_errcode_t test_ll_insert_after_tail_links(void) { + struct wolfsentry_list_header list; + struct wolfsentry_list_ent_header a, b, c; + struct wolfsentry_list_ent_header sentinel; + const struct wolfsentry_list_ent_header *cursor; + wolfsentry_hitcount_t n; - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_LIST_HEADER_RESET(list); + wolfsentry_list_ent_append(&list, &a); + wolfsentry_list_ent_append(&list, &b); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); + /* a freshly allocated entry carries an unrelated non-NULL next, which a + * correct tail insertion must overwrite with NULL. + */ + sentinel.prev = NULL; + sentinel.next = NULL; + c.prev = &sentinel; + c.next = &sentinel; + + wolfsentry_list_ent_insert_after(&list, list.tail, &c); + + if (list.tail != &c) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.tail->next != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.head->prev != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + n = 0; + for (cursor = list.head; cursor != NULL; cursor = cursor->next) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->next == NULL) && (cursor != list.tail)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); - memcpy(remote.sa.addr,"\0\1\2\3",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\377\376\375\374",sizeof local.addr_buf); - WOLFSENTRY_EXIT_ON_FALSE( - WOLFSENTRY_SUCCESS_CODE_IS( - wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results), - USED_FALLBACK)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == (WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD)); + n = 0; + for (cursor = list.tail; cursor != NULL; cursor = cursor->prev) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->prev == NULL) && (cursor != list.head)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); - flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); +static int test_static_routes(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + int n_deleted; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t inexact_matches; - /* now test eventless dispatch using wildcard/prefix matches in the static table. */ + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local, remote_wildcard, local_wildcard; + struct wolfsentry_eventconfig config = { +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + .route_private_data_size = PRIVATE_DATA_SIZE, + .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, + .max_connection_count = 10, + .derogatory_threshold_for_penaltybox = 4, + .penaltybox_duration = 1, /* denominated in seconds when passing to wolfsentry_init(). */ + .route_idle_time_for_purge = 0, + .flags = WOLFSENTRY_EVENTCONFIG_FLAG_NONE +#else + PRIVATE_DATA_SIZE, + PRIVATE_DATA_ALIGNMENT, + 10, + 4, + 1, /* denominated in seconds when passing to wolfsentry_init(). */ + 0, + WOLFSENTRY_EVENTCONFIG_FLAG_NONE, + 0, + 0, + 0, + 0, + 0, + 0 +#endif + }; - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); - WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - memcpy(remote.sa.addr,"\4\5\6\7",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS, flags_wildcard; - for (prefixlen = sizeof remote.addr_buf * BITS_PER_BYTE; - prefixlen >= 8; - --prefixlen) { - remote.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route_ref; + wolfsentry_ent_id_t route_id; + int prefixlen; + byte *private_data; + size_t private_data_size; - remote.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FALSE(route_id == id); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_TRUE(prefixlen < (int)(sizeof remote.addr_buf * BITS_PER_BYTE) ? ! WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD) : WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD)); + WOLFSENTRY_EXIT_ON_FAILURE(test_ll_insert_before_head_links()); + WOLFSENTRY_EXIT_ON_FAILURE(test_ll_insert_after_tail_links()); - remote.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); - WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr,"\0\1\2\3",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\377\376\375\374",sizeof local.addr_buf); - local.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_TRUE(prefixlen < (int)(sizeof local.addr_buf * BITS_PER_BYTE) ? ! WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD) : WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); - local.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); - WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); - } + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_FALLTHROUGH)); - remote_wildcard = remote; - local_wildcard = local; - flags_wildcard = flags; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_REJECT)); - remote_wildcard.sa.sa_port = 0; - WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); - { - struct wolfsentry_route *new_route = NULL; + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_FALLTHROUGH)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_and_check_out(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &new_route, &action_results)); - id = wolfsentry_get_object_id(new_route); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, new_route, &action_results)); +#ifdef WOLFSENTRY_UNITTEST_BENCHMARKS + { + struct timespec start_at, end_at; + uint64_t start_at_cycles, end_at_cycles; + int i; + double ns_per_call, cycles_per_call; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &start_at)); + start_at_cycles = get_intel_cycles(); + for (i=0; i<1000000; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + } + end_at_cycles = get_intel_cycles(); + WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &end_at)); + ns_per_call = ((double)(end_at.tv_sec - start_at.tv_sec) * 1000000000.0 + (double)(end_at.tv_nsec - start_at.tv_nsec)) / (double)i; + cycles_per_call = (double)(end_at_cycles - start_at_cycles) / (double)i; + printf("benchmark wolfsentry_route_event_dispatch() with empty route table: %.2f ns/call %.2f cycles/call\n", ns_per_call, cycles_per_call); +#ifdef WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE + if (cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE) { + (void)fprintf(stderr, "benchmark wolfsentry_route_event_dispatch() with empty route table: measured %.2f cycles/call exceeds max %.2f\n", cycles_per_call, (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE); + WOLFSENTRY_EXIT_ON_TRUE(cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_EMPTY_TABLE); + } +#endif } +#endif /* WOLFSENTRY_UNITTEST_BENCHMARKS */ - local.sa.sa_port = 8765; - WOLFSENTRY_EXIT_ON_FALSE( - WOLFSENTRY_SUCCESS_CODE_IS( - wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results), - USED_FALLBACK)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == (WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD)); - local.sa.sa_port = local_wildcard.sa.sa_port; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); + /* non-decisional default_policy values (STOP/ERROR alone) must be rejected. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_STOP)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ERROR)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, route_id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + memcpy(remote.sa.addr,"\4\5\6\7",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); - remote_wildcard = remote; - local_wildcard = local; - flags_wildcard = flags; + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - local_wildcard.sa.sa_port = 0; - WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); +#if 0 + puts("table after first 2 inserts:"); + for (struct wolfsentry_route *i = (struct wolfsentry_route *)wolfsentry->routes.header.head; + i; + i = (struct wolfsentry_route *)(i->header.next)) + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_render(i, stdout)); + putchar('\n'); +#endif - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(route_id == id); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD)); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); - /* make sure the fixup (clamp_wildcard_fields_to_zero()) works as expected. */ - local_wildcard.sa.sa_port = 123; + WOLFSENTRY_EXIT_ON_SUCCESS(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + WOLFSENTRY_EXIT_ON_SUCCESS(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); - remote_wildcard = remote; - local_wildcard = local; - flags_wildcard = flags; + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); + memcpy(remote.sa.addr,"\3\4\5\6",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); - remote_wildcard.sa.sa_port = local_wildcard.sa.sa_port = 0; - remote_wildcard.sa.sa_proto = local_wildcard.sa.sa_proto = 0; - WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD); - WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); - WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); + memcpy(remote.sa.addr,"\2\3\4\5",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, - &route_id, &inexact_matches, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(route_id == id); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); - WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + WOLFSENTRY_EXIT_ON_SUCCESS(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); - remote_wildcard = remote; - local_wildcard = local; + flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &remote.sa, + &local.sa, + flags, + 0 /* event_label_len */, + 0 /* event_label */, + 1 /* exact_p */, + &inexact_matches, + &route_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_ROUTE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_private_data( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route_ref, + (void **)&private_data, + &private_data_size)); + + if (private_data_size < PRIVATE_DATA_SIZE) { + printf("private_data_size is " SIZET_FMT " but expected %d.\n",private_data_size,PRIVATE_DATA_SIZE); + WOLFSENTRY_ERROR_RETURN(NOT_OK); + } + if ((PRIVATE_DATA_ALIGNMENT > 0) && ((uintptr_t)private_data % (uintptr_t)PRIVATE_DATA_ALIGNMENT)) { + printf("private_data (%p) is not aligned to %d.\n", private_data, PRIVATE_DATA_ALIGNMENT); + WOLFSENTRY_ERROR_RETURN(NOT_OK); + } + + { + byte *i; + const byte *i_end; + for (i = private_data, i_end = (private_data + private_data_size); i < i_end; ++i) + *i = 'x'; + } + +#if 0 + puts("table after deleting 4.5.6.7 and inserting 3 more:"); + for (struct wolfsentry_route *i = (struct wolfsentry_route *)wolfsentry->routes.header.head; + i; + i = (struct wolfsentry_route *)(i->header.next)) + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_render(i, stdout)); +#endif + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + /* now test basic eventless dispatch using exact-match ents in the static table. */ + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + memcpy(remote.sa.addr,"\3\4\5\6",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); + + flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_SUCCESS_CODE_IS( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results), + USED_FALLBACK)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == (WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD)); + + memcpy(remote.sa.addr,"\2\3\4\5",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); + + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); + + memcpy(remote.sa.addr,"\0\1\2\3",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\377\376\375\374",sizeof local.addr_buf); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_SUCCESS_CODE_IS( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results), + USED_FALLBACK)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == (WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD)); + + flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == 0); + + + /* now test eventless dispatch using wildcard/prefix matches in the static table. */ + + + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + memcpy(remote.sa.addr,"\4\5\6\7",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\373\372\371\370",sizeof local.addr_buf); + + for (prefixlen = sizeof remote.addr_buf * BITS_PER_BYTE; + prefixlen >= 8; + --prefixlen) { + remote.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + + remote.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(prefixlen < (int)(sizeof remote.addr_buf * BITS_PER_BYTE) ? ! WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD) : WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD)); + + remote.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + + + local.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + + local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(prefixlen < (int)(sizeof local.addr_buf * BITS_PER_BYTE) ? ! WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD) : WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD)); + + local.sa.addr_len = (wolfsentry_addr_bits_t)prefixlen; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + + } + + + remote_wildcard = remote; + local_wildcard = local; + flags_wildcard = flags; + + remote_wildcard.sa.sa_port = 0; + WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + + { + struct wolfsentry_route *new_route = NULL; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_and_check_out(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &new_route, &action_results)); + + id = wolfsentry_get_object_id(new_route); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, new_route, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + } + + local.sa.sa_port = 8765; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_SUCCESS_CODE_IS( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results), + USED_FALLBACK)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == (WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD)); + + local.sa.sa_port = local_wildcard.sa.sa_port; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, route_id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + remote_wildcard = remote; + local_wildcard = local; + flags_wildcard = flags; + + local_wildcard.sa.sa_port = 0; + WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD)); + + /* make sure the fixup (clamp_wildcard_fields_to_zero()) works as expected. */ + local_wildcard.sa.sa_port = 123; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + + + remote_wildcard = remote; + local_wildcard = local; + flags_wildcard = flags; + + remote_wildcard.sa.sa_port = local_wildcard.sa.sa_port = 0; + remote_wildcard.sa.sa_proto = local_wildcard.sa.sa_proto = 0; + WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD); + WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + WOLFSENTRY_SET_BITS(flags_wildcard, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, NULL /* event_label */, 0 /* event_label_len */, NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote_wildcard.sa, &local_wildcard.sa, flags_wildcard, 0 /* event_label_len */, 0 /* event_label */, &action_results, &n_deleted)); + WOLFSENTRY_EXIT_ON_FALSE(n_deleted == 1); + + + remote_wildcard = remote; + local_wildcard = local; flags_wildcard = flags; local_wildcard.sa.addr_len = 0; @@ -2832,59 +3458,37 @@ static int test_static_routes(void) { WOLFSENTRY_RETURN_OK; } -#endif /* TEST_STATIC_ROUTES */ +struct regression_sockaddr_v4 { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; +}; -#ifdef TEST_DYNAMIC_RULES - -static wolfsentry_errcode_t wolfsentry_action_dummy_callback( - WOLFSENTRY_CONTEXT_ARGS_IN, - const struct wolfsentry_action *action, - void *handler_context, - void *caller_arg, - const struct wolfsentry_event *event, - wolfsentry_action_type_t action_type, - const struct wolfsentry_route *target_route, - struct wolfsentry_route_table *route_table, - struct wolfsentry_route *rule_route, - wolfsentry_action_res_t *action_results) -{ - WOLFSENTRY_CONTEXT_ARGS_NOT_USED; - (void)action; - (void)handler_context; - (void)caller_arg; - (void)event; - (void)action_type; - (void)target_route; - (void)route_table; - (void)rule_route; - (void)action_results; - - WOLFSENTRY_RETURN_OK; +static void regression_sockaddr_v4_init(struct regression_sockaddr_v4 *sa, const byte *addr, wolfsentry_port_t port, wolfsentry_proto_t proto, byte interface) { + memset(sa, 0, sizeof *sa); + sa->sa.sa_family = AF_INET; + sa->sa.sa_proto = proto; + sa->sa.sa_port = port; + sa->sa.addr_len = sizeof sa->addr_buf * BITS_PER_BYTE; + sa->sa.interface = interface; + memcpy(sa->sa.addr, addr, sizeof sa->addr_buf); } +#define REGRESSION_AF_WILDCARD_FLAGS (WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD) -static int test_dynamic_rules(void) { - +static int test_route_lookup_af_wildcard_span_terminates(void) { struct wolfsentry_context *wolfsentry; -#if 0 wolfsentry_action_res_t action_results; - int n_deleted; - int ret; - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } src, dst; -#endif - - wolfsentry_ent_id_t id; - -#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS - static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; - static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; -#else - static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -#endif + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id_inet, id_wild_in, id_wild_out, route_id; + struct regression_sockaddr_v4 remote, local, remote_wild, local_wild; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -2892,575 +3496,717 @@ static int test_dynamic_rules(void) { wolfsentry_init_ex( wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), - &config, + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "connect", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_inet, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE( - WOLFSENTRY_ERROR_CODE_IS( - wolfsentry_event_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_BUILTIN_LABEL_PREFIX "connect", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id), - NOT_PERMITTED)); + memset(&remote_wild, 0, sizeof remote_wild); + memset(&local_wild, 0, sizeof local_wild); - /* track port scanning */ WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "connection_refused", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + NULL /* caller_arg */, + &remote_wild.sa, &local_wild.sa, + REGRESSION_AF_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + &id_wild_in, &action_results)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "disconnect", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + NULL /* caller_arg */, + &remote_wild.sa, &local_wild.sa, + REGRESSION_AF_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, + NULL /* event_label */, 0 /* event_label_len */, + &id_wild_out, &action_results)); + + /* a port with no exact match forces the full reverse scan through the + * wildcard-AF span. + */ + remote.sa.sa_port = 54321; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_event_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, - "authentication_succeeded", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_wild_in); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD)); + + remote.sa.sa_port = 12345; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_event_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, - "negotiation_abandoned", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); - - { - wolfsentry_ent_id_t auth_failed_id; - struct wolfsentry_event *auth_failed_event; - const struct wolfsentry_event *insertion_side_effect_demo_event; - const char *insertion_side_effect_demo_label; + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_inet); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "authentication_failed", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_wild_out, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_wild_in, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_inet, NULL /* event_label */, 0 /* event_label_len */, &action_results)); - auth_failed_id = id; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "insertion_side_effect_demo", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_set_aux_event( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "authentication_failed", - -1, - "insertion_side_effect_demo", - -1)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_get_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "authentication_failed", - -1, - &auth_failed_event)); +static int test_route_lookup_exact_match_honors_exclude_reject(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id_pbox, route_id; + struct regression_sockaddr_v4 remote, local; - WOLFSENTRY_EXIT_ON_FALSE(auth_failed_id == wolfsentry_get_object_id(auth_failed_event)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FALSE( - insertion_side_effect_demo_event = wolfsentry_event_get_aux_event(auth_failed_event) - ); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FALSE( - insertion_side_effect_demo_label = wolfsentry_event_get_label(insertion_side_effect_demo_event) - ); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(insertion_side_effect_demo_label, "insertion_side_effect_demo") == 0); + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_drop_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - auth_failed_event, - NULL /* action_results */)); - } + /* the parent-event wildcard makes the route key identical to the lookup + * target, so the exact-seek short circuit is the path under test. + */ + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1 /* label_len */, - 10, - NULL /* config */, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_pbox, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == id_pbox); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_insert( + wolfsentry_route_event_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, - "deletion_side_effect_demo", - -1 /* label_len */, - 10, - &config2, - WOLFSENTRY_EVENT_FLAG_NONE, - &id)); + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_pbox); - { - struct wolfsentry_eventconfig eventconfig; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_pbox, NULL /* event_label */, 0 /* event_label_len */, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_get_config( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "deletion_side_effect_demo", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &eventconfig)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config2.route_private_data_size) && - (eventconfig.route_private_data_alignment == config2.route_private_data_alignment) && - (eventconfig.max_connection_count == config2.max_connection_count)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_update_config( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "deletion_side_effect_demo", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &config)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_get_config( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "deletion_side_effect_demo", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &eventconfig)); +static int test_route_lookup_action_res_filter_bits_unset(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig event_config; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t event_id, id_filtered, route_id; + struct regression_sockaddr_v4 remote, local; + static const char event_label[] = "filter-bits-unset-test"; - WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config.route_private_data_size) && - (eventconfig.route_private_data_alignment == config.route_private_data_alignment) && - (eventconfig.max_connection_count == config.max_connection_count)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "deletion_side_effect_demo", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_SUCCESS( - wolfsentry_event_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "deletion_side_effect_demo", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - NULL /* action_results */)); - } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &event_config)); + event_config.action_res_filter_bits_unset = WOLFSENTRY_ACTION_RES_USER2; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "insert_always", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + &event_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &event_id)); - WOLFSENTRY_EXIT_ON_FALSE( - WOLFSENTRY_ERROR_CODE_IS( - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_BUILTIN_LABEL_PREFIX "insert_always", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id), - NOT_PERMITTED)); + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "insert_alway", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id_filtered, &action_results)); - { - static char too_long_label[WOLFSENTRY_MAX_LABEL_BYTES + 2]; - memset(too_long_label, 'x', sizeof too_long_label - 1); + /* the forbidden bit is present, so the route must be skipped. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_USER2); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == id_filtered); - too_long_label[sizeof too_long_label - 1] = 0; + /* a different user bit is not forbidden, so the route must match. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_USER1); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_filtered); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - STRING_ARG_TOO_LONG, - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_filtered, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - STRING_ARG_TOO_LONG, - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - sizeof too_long_label - 1, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - sizeof too_long_label - 2, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - STRING_ARG_TOO_LONG, - wolfsentry_action_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - sizeof too_long_label - 1, - NULL /* action_results */)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - STRING_ARG_TOO_LONG, - wolfsentry_action_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - -1, - NULL /* action_results */)); +static int test_route_lookup_full_scan_honors_exclude_reject(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id_pbox, route_id; + struct regression_sockaddr_v4 remote, local; - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_action_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - NULL, - -1, - NULL /* action_results */)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - too_long_label[sizeof too_long_label - 2] = 0; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - ITEM_ALREADY_PRESENT, - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - NULL, - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\0", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - 0 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + /* a /24 rule never seeks to an exact hit for a /32 target, so the reverse + * scan is the path under test. + */ + remote.sa.addr_len = 24; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, + NULL /* event_label */, 0 /* event_label_len */, + &id_pbox, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - too_long_label, - -1 /* label_len */, - NULL /* action_results */)); - } + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == id_pbox); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( + wolfsentry_route_event_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, - "set_connect_wildcards", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_pbox); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_pbox, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t regression_failing_insert_action( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_context; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + WOLFSENTRY_ERROR_RETURN(NOT_PERMITTED); +} + +static int test_route_insert_action_failure_detaches_route(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig event_config; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t id, route_id; + wolfsentry_hitcount_t n_ents_before, purge_len_before; + const struct wolfsentry_route *last_af_wildcard_before; + wolfsentry_priority_t highest_priority_before; + struct regression_sockaddr_v4 remote, local, remote_wild, local_wild; + static const char event_label[] = "failing-insert-parent"; + static const char action_label[] = "fail-on-insert"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "set_connectionreset_wildcards", - -1 /* label_len */, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, + regression_failing_insert_action, NULL /* handler_context */, &id)); + /* a purge deadline puts the route on the purge list, which must be undone + * along with the wildcard-AF bookkeeping when the insert is rolled back. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &event_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 3600, 0, &event_config.route_idle_time_for_purge)); + WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "increment_derogatory", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + &event_config, + WOLFSENTRY_EVENT_FLAG_NONE, &id)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( + wolfsentry_event_action_append( WOLFSENTRY_CONTEXT_ARGS_OUT, - "increment_commendable", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, - &id)); + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + n_ents_before = wolfsentry->routes->header.n_ents; + purge_len_before = wolfsentry->routes->purge_list.len; + last_af_wildcard_before = wolfsentry->routes->last_af_wildcard_route; + highest_priority_before = wolfsentry->routes->highest_priority_route_in_table; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + memset(&remote_wild, 0, sizeof remote_wild); + memset(&local_wild, 0, sizeof local_wild); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_wild.sa, &local_wild.sa, + REGRESSION_AF_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->header.n_ents == n_ents_before); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->purge_list.len == purge_len_before); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->last_af_wildcard_route == last_af_wildcard_before); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->highest_priority_route_in_table == highest_priority_before); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + + /* the lookup and purge paths that consult the auxiliary structures must + * still work after the rollback. + */ + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, action_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static int test_route_insert_action_failure_releases_bitmask_af(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_addr_family_t n_bitmask_afs_before; + struct wolfsentry_route_exports exp_route; + static const byte exp_remote_addr_and_mask[8] = { 0xC0, 0xA8, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0x00 }; + static const char event_label[] = "failing-insert-bitmask-parent"; + static const char action_label[] = "fail-on-bitmask-insert"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "check_counts", - -1 /* label_len */, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, + regression_failing_insert_action, NULL /* handler_context */, &id)); - { - struct wolfsentry_action *action; - wolfsentry_action_flags_t flags; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - ITEM_NOT_FOUND, - wolfsentry_action_get_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "checXXXounts", - -1 /* label_len */, - &action)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - INVALID_ARG, - wolfsentry_action_get_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "checXXXounts", - 0 /* label_len */, - &action)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + n_bitmask_afs_before = wolfsentry->routes->n_bitmask_matching_afs; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - STRING_ARG_TOO_LONG, - wolfsentry_action_get_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "checXXXounts", - WOLFSENTRY_MAX_LABEL_BYTES + 1 /* label_len */, - &action)); + memset(&exp_route, 0, sizeof exp_route); + exp_route.parent_event_label = event_label; + exp_route.parent_event_label_len = WOLFSENTRY_LENGTH_NULL_TERMINATED; + exp_route.sa_family = AF_INET; + exp_route.sa_proto = IPPROTO_TCP; + exp_route.flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + exp_route.remote_address = exp_remote_addr_and_mask; + exp_route.remote.addr_len = sizeof exp_remote_addr_and_mask * BITS_PER_BYTE; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_get_reference( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "check_counts", - -1 /* label_len */, - &action)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_get_flags( - action, - &flags)); - WOLFSENTRY_EXIT_ON_FALSE(flags == WOLFSENTRY_ACTION_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->n_bitmask_matching_afs == n_bitmask_afs_before); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - { - wolfsentry_action_flags_t flags_before, flags_after; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_update_flags( - action, - WOLFSENTRY_ACTION_FLAG_DISABLED, - WOLFSENTRY_ACTION_FLAG_NONE, - &flags_before, - &flags_after)); - WOLFSENTRY_EXIT_ON_FALSE(flags_before == WOLFSENTRY_ACTION_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FALSE(flags_after == WOLFSENTRY_ACTION_FLAG_DISABLED); - } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, action_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_get_flags( - action, - &flags)); - WOLFSENTRY_EXIT_ON_FALSE(flags == WOLFSENTRY_ACTION_FLAG_DISABLED); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - { - wolfsentry_action_flags_t flags_before, flags_after; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_update_flags( - action, - WOLFSENTRY_ACTION_FLAG_NONE, - WOLFSENTRY_ACTION_FLAG_DISABLED, - &flags_before, - &flags_after)); - WOLFSENTRY_EXIT_ON_FALSE(flags_before == WOLFSENTRY_ACTION_FLAG_DISABLED); - WOLFSENTRY_EXIT_ON_FALSE(flags_after == WOLFSENTRY_ACTION_FLAG_NONE); - } + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_get_flags( - action, - &flags)); - WOLFSENTRY_EXIT_ON_FALSE(flags == WOLFSENTRY_ACTION_FLAG_NONE); + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action, NULL)); - } +static wolfsentry_errcode_t regression_noop_insert_action( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_context; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_insert_with_actions_marks_parent_event(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id, id_route; + struct regression_sockaddr_v4 remote, local; + static const char parent_label[] = "parent-with-insert-actions"; + static const char other_label[] = "would-be-aux-user"; + static const char action_label[] = "noop-on-insert"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "add_to_greenlist", - -1 /* label_len */, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, + regression_noop_insert_action, NULL /* handler_context */, &id)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_action_insert( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "del_from_greenlist", - -1 /* label_len */, - WOLFSENTRY_ACTION_FLAG_NONE, - wolfsentry_action_dummy_callback, - NULL /* handler_context */, + parent_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, &id)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_action_append( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1, - WOLFSENTRY_ACTION_TYPE_MATCH, - "del_from_greenlist", - -1)); + other_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_ON_SUCCESS( - wolfsentry_event_action_prepend( + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1, - WOLFSENTRY_ACTION_TYPE_MATCH, - "del_from_greenlist", - -1)); + parent_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_action_prepend( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1, - WOLFSENTRY_ACTION_TYPE_MATCH, - "add_to_greenlist", - -1)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + parent_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id_route, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_action_insert_after( + /* an event that already parents a route can't become an aux event. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1, - WOLFSENTRY_ACTION_TYPE_MATCH, - "check_counts", - -1, - "add_to_greenlist", - -1)); + other_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + parent_label, WOLFSENTRY_LENGTH_NULL_TERMINATED)); - { - struct wolfsentry_action_list_ent *cursor; - const char *action_label; - int action_label_len; - static const char *labels[] = { "add_to_greenlist", "check_counts", "del_from_greenlist" }; - int label_i = 0; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_route, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, other_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, parent_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, action_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_action_list_start( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1, - WOLFSENTRY_ACTION_TYPE_MATCH, - &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - while (wolfsentry_event_action_list_next( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &cursor, - &action_label, - &action_label_len) >= 0) { - WOLFSENTRY_EXIT_ON_TRUE((size_t)label_i >= sizeof labels / sizeof labels[0]); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(action_label, labels[label_i++]) == 0); - } + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FALSE(label_i == sizeof labels / sizeof labels[0]); +static int test_route_insert_rejects_expired_purge_after(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_time_t now, one_hour; + struct wolfsentry_route_exports exp_route; + static const byte exp_remote_addr[4] = { 0, 1, 2, 3 }; + static const byte exp_local_addr[4] = { 255, 254, 253, 252 }; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_action_list_done( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &cursor)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - } + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&exp_route, 0, sizeof exp_route); + exp_route.sa_family = AF_INET; + exp_route.sa_proto = IPPROTO_TCP; + exp_route.flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + exp_route.remote_address = exp_remote_addr; + exp_route.remote.addr_len = sizeof exp_remote_addr * BITS_PER_BYTE; + exp_route.remote.sa_port = 12345; + exp_route.local_address = exp_local_addr; + exp_route.local.addr_len = sizeof exp_local_addr * BITS_PER_BYTE; + exp_route.local.sa_port = 443; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_time_now_plus_delta(wolfsentry, 0, &now)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 3600, 0, &one_hour)); + + /* a deadline at or before the insert time can never be served. */ + exp_route.meta.purge_after = now; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + exp_route.meta.purge_after = now - one_hour; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + exp_route.meta.purge_after = now + one_hour; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_event_action_delete( + wolfsentry_route_insert_by_exports( WOLFSENTRY_CONTEXT_ARGS_OUT, - "match_side_effect_demo", - -1, - WOLFSENTRY_ACTION_TYPE_MATCH, - "del_from_greenlist", - -1)); + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); @@ -3469,64 +4215,66 @@ static int test_dynamic_rules(void) { WOLFSENTRY_RETURN_OK; } -#endif /* TEST_DYNAMIC_RULES */ - -#ifdef TEST_USER_VALUES - -#include +#if !defined(WOLFSENTRY_NO_JSON) || defined(WOLFSENTRY_JSON_DUMP_UTILS) -static wolfsentry_errcode_t test_kv_validator( +/* renders a route to JSON through every output budget from empty up to the + * full length, checking that a short budget is reported rather than + * overrun. + */ +static int regression_route_format_json_sweep( WOLFSENTRY_CONTEXT_ARGS_IN, - struct wolfsentry_kv_pair *kv) + const struct wolfsentry_route *route, + unsigned char *full_buf, + size_t full_buf_size, + size_t *full_len) { - WOLFSENTRY_CONTEXT_ARGS_NOT_USED; - switch (WOLFSENTRY_KV_TYPE(kv)) { - case WOLFSENTRY_KV_NONE: - case WOLFSENTRY_KV_NULL: - case WOLFSENTRY_KV_TRUE: - case WOLFSENTRY_KV_FALSE: - WOLFSENTRY_RETURN_OK; - case WOLFSENTRY_KV_UINT: - if (WOLFSENTRY_KV_V_UINT(kv) == 12345678UL) - WOLFSENTRY_ERROR_RETURN(BAD_VALUE); - else - WOLFSENTRY_RETURN_OK; - case WOLFSENTRY_KV_SINT: - if (WOLFSENTRY_KV_V_SINT(kv) == -12345678L) - WOLFSENTRY_ERROR_RETURN(BAD_VALUE); - else - WOLFSENTRY_RETURN_OK; - case WOLFSENTRY_KV_FLOAT: - if (WOLFSENTRY_KV_V_FLOAT(kv) > 100.0) - WOLFSENTRY_ERROR_RETURN(BAD_VALUE); - else - WOLFSENTRY_RETURN_OK; - case WOLFSENTRY_KV_STRING: - if (WOLFSENTRY_KV_V_STRING_LEN(kv) != 8) - WOLFSENTRY_RETURN_OK; - if (strncmp(WOLFSENTRY_KV_V_STRING(kv), "deadbeef", WOLFSENTRY_KV_V_STRING_LEN(kv)) == 0) - WOLFSENTRY_ERROR_RETURN(BAD_VALUE); - else - WOLFSENTRY_RETURN_OK; - case WOLFSENTRY_KV_BYTES: - if (WOLFSENTRY_KV_V_BYTES_LEN(kv) != 10) - WOLFSENTRY_RETURN_OK; - if (memcmp(WOLFSENTRY_KV_V_STRING(kv), "abcdefghij", WOLFSENTRY_KV_V_BYTES_LEN(kv)) == 0) - WOLFSENTRY_ERROR_RETURN(BAD_VALUE); - else - WOLFSENTRY_RETURN_OK; - default: - break; + static const unsigned char guard_byte = 0xA5; + unsigned char guard_buf[2048]; + unsigned char *json_out; + size_t json_out_len, n, i; + wolfsentry_errcode_t ret; + + json_out = full_buf; + json_out_len = full_buf_size; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_format_json(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &json_out, &json_out_len, WOLFSENTRY_FORMAT_FLAG_NONE)); + *full_len = full_buf_size - json_out_len; + WOLFSENTRY_EXIT_ON_FALSE(*full_len > 0); + WOLFSENTRY_EXIT_ON_FALSE(*full_len + 512 < sizeof guard_buf); + + for (n = 0; n < *full_len; ++n) { + memset(guard_buf, guard_byte, sizeof guard_buf); + json_out = guard_buf; + json_out_len = n; + ret = wolfsentry_route_format_json(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &json_out, &json_out_len, WOLFSENTRY_FORMAT_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(ret, BUFFER_TOO_SMALL)); + WOLFSENTRY_EXIT_ON_FALSE((size_t)(json_out - guard_buf) <= n); + WOLFSENTRY_EXIT_ON_FALSE(json_out_len <= n); + for (i = n; i < sizeof guard_buf; ++i) + WOLFSENTRY_EXIT_ON_FALSE(guard_buf[i] == guard_byte); } - WOLFSENTRY_ERROR_RETURN(WRONG_TYPE); + + memset(guard_buf, guard_byte, sizeof guard_buf); + json_out = guard_buf; + json_out_len = *full_len; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_format_json(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &json_out, &json_out_len, WOLFSENTRY_FORMAT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(json_out_len == 0); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(guard_buf, full_buf, *full_len) == 0); + WOLFSENTRY_EXIT_ON_FALSE(guard_buf[*full_len] == guard_byte); + + WOLFSENTRY_RETURN_OK; } -static int test_user_values(void) { +static int test_route_format_json_truncation_is_bounded(void) { struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; wolfsentry_action_res_t action_results; - - wolfsentry_kv_type_t kv_type; - struct wolfsentry_kv_pair_internal *kv_ref; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id; + unsigned char full_buf[1024]; + size_t full_len; + struct regression_sockaddr_v4 remote, local; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -3538,691 +4286,975 @@ static int test_user_values(void) { &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); - action_results = WOLFSENTRY_ACTION_RES_NONE; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_set_validator(WOLFSENTRY_CONTEXT_ARGS_OUT, test_kv_validator, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(action_results == WOLFSENTRY_ACTION_RES_NONE); + regression_sockaddr_v4_init(&remote, (const byte *)"\xC0\xA8\x01\x01", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\12\0\0\1", 443, IPPROTO_TCP, 2); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_null( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_null", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 0)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_bool( + wolfsentry_route_get_reference( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_KV_TRUE, - 0)); + main_routes, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + 1 /* exact_p */, + &inexact_matches, + &route)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_format_json_sweep(WOLFSENTRY_CONTEXT_ARGS_OUT, route, full_buf, sizeof full_buf, &full_len)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_bool( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &kv_type)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_TRUE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_format_json_escapes_parent_event_label(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id; + unsigned char full_buf[1024]; + size_t full_len; + struct regression_sockaddr_v4 remote, local; + static const char event_label[] = "q\"b\\s\001c"; + static const char expected[] = "\"parent-event\":\"q\\\"b\\\\s\\u0001c\","; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_bool( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_KV_FALSE, - 1)); + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_bool( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, - &kv_type)); + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_FALSE); + regression_sockaddr_v4_init(&remote, (const byte *)"\xC0\xA8\x01\x01", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\12\0\0\1", 443, IPPROTO_TCP, 2); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - WRONG_TYPE, - wolfsentry_user_value_store_bool( + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_KV_NONE, - 1)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_type( + wolfsentry_route_get_reference( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &kv_type)); + main_routes, + &remote.sa, &local.sa, flags, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 1 /* exact_p */, + &inexact_matches, + &route)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_format_json_sweep(WOLFSENTRY_CONTEXT_ARGS_OUT, route, full_buf, sizeof full_buf - 1, &full_len)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_FALSE); + full_buf[full_len] = 0; + WOLFSENTRY_EXIT_ON_FALSE(strstr((const char *)full_buf, expected) != NULL); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_type( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - strlen("test_bool"), - &kv_type)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_FALSE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - ITEM_NOT_FOUND, - wolfsentry_user_value_get_type( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bool", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &kv_type)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_uint( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_uint", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 123UL, - 0)); +#ifndef WOLFSENTRY_NO_GETPROTOBY +#include +#endif - { - uint64_t value = 0; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_uint( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_uint", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value)); +static int test_route_format_json_unknown_protocol_numeric(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id; + unsigned char full_buf[1024]; + size_t full_len, i; + struct regression_sockaddr_v4 remote, local; + wolfsentry_proto_t unknown_proto = 0; + char expected[32]; + /* IANA experimental and unassigned numbers, checked against the host + * database at runtime because a local protocols file can name any of + * them. + */ + static const wolfsentry_proto_t candidates[] = { 253, 254, 143, 144, 145 }; - WOLFSENTRY_EXIT_ON_FALSE(value == 123UL); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + +#ifndef WOLFSENTRY_NO_GETPROTOBY + for (i = 0; i < sizeof candidates / sizeof candidates[0]; ++i) { + if (getprotobynumber((int)candidates[i]) == NULL) { + unknown_proto = candidates[i]; + break; + } + } + if (unknown_proto == 0) { + printf("skipping unknown-protocol JSON check: host protocol database names every candidate.\n"); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; } +#else + (void)i; + unknown_proto = candidates[0]; +#endif + WOLFSENTRY_EXIT_ON_FALSE(snprintf(expected, sizeof expected, "\"protocol\":%u,", (unsigned int)unknown_proto) > 0); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - BAD_VALUE, - wolfsentry_user_value_store_uint( + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\xC0\xA8\x01\x01", 0, unknown_proto, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\12\0\0\1", 0, unknown_proto, 2); + flags = WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "bad_uint", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 12345678UL, - 0)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_sint( + wolfsentry_route_get_reference( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_sint", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - -123L, - 0)); + main_routes, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + 1 /* exact_p */, + &inexact_matches, + &route)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_format_json_sweep(WOLFSENTRY_CONTEXT_ARGS_OUT, route, full_buf, sizeof full_buf - 1, &full_len)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - { - int64_t value = 0; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_sint( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_sint", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value)); + full_buf[full_len] = 0; + WOLFSENTRY_EXIT_ON_FALSE(strstr((const char *)full_buf, expected) != NULL); - WOLFSENTRY_EXIT_ON_FALSE(value == -123L); - } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - BAD_VALUE, - wolfsentry_user_value_store_sint( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "bad_sint", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - -12345678L, - 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* !WOLFSENTRY_NO_JSON || WOLFSENTRY_JSON_DUMP_UTILS */ + +static int test_static_routes_clone_addr_family_links(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *clone = NULL; + wolfsentry_action_res_t action_results; + const struct wolfsentry_table_ent_header *i, *prev; + wolfsentry_addr_bits_t bits; + unsigned int n; + static const char * const names[3] = { "clone_AF_0", "clone_AF_1", "clone_AF_2" }; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_double( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_float", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 1.234, - 0)); + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - { - double value = 0.0; + for (n = 0; n < 3; ++n) { WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_float( + wolfsentry_addr_family_handler_install( WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_float", + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + n), + names[n], WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value)); - - WOLFSENTRY_EXIT_ON_FALSE(fabs(value - 1.234) < 0.000001); + my_addr_family_parser, + my_addr_family_formatter, + (int)(8 * (n + 1)) /* max_addr_bits */)); } - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - BAD_VALUE, - wolfsentry_user_value_store_double( + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_context_clone( WOLFSENTRY_CONTEXT_ARGS_OUT, - "bad_float", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 123.45678, - 0)); + &clone, + WOLFSENTRY_CLONE_FLAG_NONE)); + + prev = NULL; + n = 0; + for (i = clone->addr_families_bynumber->header.head; i != NULL; i = i->next) { + WOLFSENTRY_EXIT_ON_FALSE(i->prev == prev); + WOLFSENTRY_EXIT_ON_FALSE(i->parent_table == &clone->addr_families_bynumber->header); + prev = i; + ++n; + } + WOLFSENTRY_EXIT_ON_FALSE(n == 3); + WOLFSENTRY_EXIT_ON_FALSE(n == clone->addr_families_bynumber->header.n_ents); + WOLFSENTRY_EXIT_ON_FALSE(prev == clone->addr_families_bynumber->header.tail); - { - static const char test_string[] = "abc123"; - const char *value = NULL; - int value_len = -1; - int mutable = -1; - wolfsentry_errcode_t ret; + /* removing the middle family from the clone must leave its neighbors + * reachable. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_remove_bynumber( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 1), + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_string( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - test_string, - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 0)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)WOLFSENTRY_AF_USER_OFFSET, + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 8); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 1), + &bits)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 2), + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 24); + + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.n_ents == 2); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.head != NULL); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.head->prev == NULL); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.head->next == clone->addr_families_bynumber->header.tail); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.tail->prev == clone->addr_families_bynumber->header.head); + + /* the source context must be untouched by edits to the clone. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 1), + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 16); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_mutability( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &mutable)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); - WOLFSENTRY_EXIT_ON_FALSE(mutable == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_set_mutability( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 0)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_mutability( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &mutable)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FALSE(mutable == 0); +static int test_static_routes_id_wrap(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id1, id2; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; - WOLFSENTRY_EXIT_ON_SUCCESS( - ret = wolfsentry_user_value_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NOT_PERMITTED, ret); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_string( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, - &value_len, - &kv_ref)); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); - WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(test_string)); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, test_string) == 0); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_release_record( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &kv_ref)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + wolfsentry->mk_id_cb_state.id_counter = (wolfsentry_ent_id_t)MAX_UINT_OF(wolfsentry_ent_id_t) - 1U; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - } + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id1, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id1 == (wolfsentry_ent_id_t)MAX_UINT_OF(wolfsentry_ent_id_t)); + + /* the next allocation wraps the counter and must still yield a valid, + * unique ID. + */ + local.sa.sa_port = 444; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id2, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id2 != WOLFSENTRY_ENT_ID_NONE); + WOLFSENTRY_EXIT_ON_FALSE(id2 != id1); -#ifdef WOLFSENTRY_HAVE_JSON_DOM - /* strings rendered as JSON must escape quotes and backslashes. */ - { - static const char raw[] = "a\"b\\c"; - const struct wolfsentry_kv_pair *kv_exports; - char render_buf[64]; - int render_buf_space = (int)sizeof render_buf; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id2, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id1, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); - memset(render_buf, 0, sizeof render_buf); + /* after the wrap the counter must never rest on the reserved null ID. */ + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter != WOLFSENTRY_ENT_ID_NONE); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_store_string( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "json_escape_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - raw, - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 0)); - { - const char *value = NULL; - int value_len = -1; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_get_string( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "json_escape_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, &value_len, &kv_ref)); - } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, render_buf, &render_buf_space)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(strstr(render_buf, "\\\"") != NULL); - WOLFSENTRY_EXIT_ON_FALSE(strstr(render_buf, "\\\\") != NULL); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_release_record(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_ref)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_delete( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "json_escape_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED)); - } -#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - BAD_VALUE, - wolfsentry_user_value_store_string( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "bad_string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - "deadbeef", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - 0)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - { - static const byte test_bytes[] = { 0, 1, 2, 3, 4 }; - const byte *value = NULL; - int value_len = -1; + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_store_bytes( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bytes", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - test_bytes, - sizeof test_bytes, - 0)); +static int test_static_routes_id_reclaim(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id1, id2, id3, counter_after_insert; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS; + unsigned int n; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_bytes( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "test_bytes", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, - &value_len, - &kv_ref)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)sizeof test_bytes); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(value, test_bytes, (size_t)value_len) == 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id1, &action_results)); + counter_after_insert = wolfsentry->mk_id_cb_state.id_counter; + WOLFSENTRY_EXIT_ON_FALSE(id1 == counter_after_insert); + + /* deleting and re-inserting the newest entry repeatedly must reuse its + * ID rather than grow the counter. + */ + for (n = 0; n < 8; ++n) { WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_release_record( + wolfsentry_route_delete_by_id( WOLFSENTRY_CONTEXT_ARGS_OUT, - &kv_ref)); + NULL /* caller_arg */, + id1, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == counter_after_insert - 1U); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id2, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id2 == id1); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == counter_after_insert); } + /* deleting an older entry must not disturb the counter, and the next + * allocation must not collide with the surviving newest entry. + */ + local.sa.sa_port = 444; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id2, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id2 == counter_after_insert + 1U); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id1, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == id2); + local.sa.sa_port = 445; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id3, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id3 == id2 + 1U); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id3, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id2, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == id2 - 1U); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_insert_requires_direction(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 4001; + local.sa.sa_port = 4002; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x0d", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\x0a\x0b\x0c\x0e", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - BAD_VALUE, - wolfsentry_user_value_store_bytes( + INVALID_ARG, + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "bad_bytes", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (const byte *)"abcdefghij", - 10, - 0)); + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); -#ifndef WOLFSENTRY_NO_STDIO - { - wolfsentry_errcode_t ret; - struct wolfsentry_cursor *cursor; - const struct wolfsentry_kv_pair *kv_exports; - const char *val_type; - char val_buf[256]; - int val_buf_space; - wolfsentry_hitcount_t n_seen = 0; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); - for (ret = wolfsentry_user_values_iterate_current(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref); - ret >= 0; - ret = wolfsentry_user_values_iterate_next(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref)) { - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); - val_buf_space = sizeof val_buf; - if (wolfsentry_kv_type_to_string(WOLFSENTRY_KV_TYPE(kv_exports), &val_type) < 0) - val_type = "?"; - if (wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, val_buf, &val_buf_space) < 0) - strcpy(val_buf,"?"); - printf("{ \"%.*s\" : { \"%s\" : %s } }\n", - (int)WOLFSENTRY_KV_KEY_LEN(kv_exports), - WOLFSENTRY_KV_KEY(kv_exports), - val_type, - val_buf); - ++n_seen; - } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->user_values->header.n_ents); - WOLFSENTRY_EXIT_ON_FALSE(n_seen == 6); - } -#endif + /* the same route is accepted once it has a direction. */ + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); - { - static const struct { - const char *q; - const char *a; - } base64_qna[] = { - { "", "" }, - { "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu", "Many hands make light work." }, - { "bGlnaHQgd29yay4=", "light work." }, - { "bGlnaHQgd29yay4", "light work." }, - { "bGlnaHQgd29yaw==", "light work" }, - { "bGlnaHQgd29yaw", "light work" }, - { "bGlnaHQgd29y", "light wor" }, - { "bGlnaHQgd28=", "light wo" }, - { "bGlnaHQgdw==", "light w" }, - { "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV" - "1hZWjAxMjM0NTY3ODkhQCMkJV4mKigpXy0rPXxcYH5bXXt9OzonIiw8Lj4vPw==", - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - "!@#$%^&*()_-+=|\\`~[]{};:'\",<.>/?" }, - { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/+", - "\x69\xb7\x1d\x79\xf8\x21\x8a\x39\x25\x9a\x7a\x29\xaa\xbb\x2d\xba" - "\xfc\x31\xcb\x30\x01\x08\x31\x05\x18\x72\x09\x28\xb3\x0d\x38\xf4" - "\x11\x49\x35\x15\x59\x76\x19\xd3\x5d\xb7\xe3\x9e\xbb\xf3\xdf\xfe" - } - }; + return 0; +} - int i; - byte outbuf[256]; - size_t outbuf_spc; +static int test_route_insert_by_exports_requires_direction(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route_exports exp_route; + byte exp_remote_addr[4]; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; - for (i=0; i < (int)(sizeof base64_qna / sizeof base64_qna[0]); ++i) { - outbuf_spc = sizeof outbuf; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_base64_decode(base64_qna[i].q, strlen(base64_qna[i].q), outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); - WOLFSENTRY_EXIT_ON_FALSE((outbuf_spc == strlen(base64_qna[i].a)) && (memcmp(outbuf, base64_qna[i].a, outbuf_spc) == 0)); - } + memset(&exp_route, 0, sizeof exp_route); + exp_route.sa_family = AF_INET; + exp_route.sa_proto = IPPROTO_TCP; + exp_route.flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + memcpy(exp_remote_addr, "\x0a\x0b\x0c\x0d", sizeof exp_remote_addr); + exp_route.remote_address = exp_remote_addr; + exp_route.remote.addr_len = sizeof exp_remote_addr * BITS_PER_BYTE; + exp_route.local.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + /* the same route is accepted once it has a direction. */ + WOLFSENTRY_SET_BITS(exp_route.flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} + +static int test_route_flags_sensical_validation(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t proto_flags, all_flags; + unsigned int i; + static const wolfsentry_route_flags_t family_deps[] = { + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD + }; + static const wolfsentry_route_flags_t proto_deps[] = { + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + }; + + proto_flags = WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD; + all_flags = proto_flags + | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_check_flags_sensical(WOLFSENTRY_ROUTE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_check_flags_sensical(proto_flags)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_check_flags_sensical(all_flags)); + + /* a family wildcard needs both address wildcards and the protocol wildcard. */ + for (i = 0; i < sizeof family_deps / sizeof family_deps[0]; ++i) { + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_check_flags_sensical(all_flags & ~family_deps[i])); } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + /* a protocol wildcard needs both port wildcards. */ + for (i = 0; i < sizeof proto_deps / sizeof proto_deps[0]; ++i) { + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_check_flags_sensical(proto_flags & ~proto_deps[i])); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_check_flags_sensical(all_flags & ~proto_deps[i])); + } - WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + /* the validation also applies on the insert path. */ + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x0f", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\x0a\x0b\x0c\x10", sizeof local.addr_buf); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD | proto_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + return 0; } -#endif /* TEST_USER_VALUES */ +static int insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_IN, struct wolfsentry_route **route_out, wolfsentry_ent_id_t *id_out) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; -#ifdef TEST_USER_ADDR_FAMILIES + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 4003; + local.sa.sa_port = 4004; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x11", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\x0a\x0b\x0c\x12", sizeof local.addr_buf); -static int test_user_addr_families(void) { + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); - struct wolfsentry_context *wolfsentry; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)route_out)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + *id_out = id; + + return 0; +} + +static int test_route_update_flags_rejects_penaltybox_and_greenlist(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route *route; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags_before, flags_after; wolfsentry_action_res_t action_results; - WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FALSE(insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_OUT, &route, &id) == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED | WOLFSENTRY_ROUTE_FLAG_GREENLISTED, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_ATOMIC_LOAD(route->flags) & (WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED | WOLFSENTRY_ROUTE_FLAG_GREENLISTED)); + /* either flag alone is accepted. */ WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_init_ex( - wolfsentry_build_settings, - WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), - NULL /* config */, - &wolfsentry, - WOLFSENTRY_INIT_FLAG_NONE)); + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_GREENLISTED, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(route->flags) & WOLFSENTRY_ROUTE_FLAG_GREENLISTED); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_install( + wolfsentry_route_delete_by_id( WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - "my_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - my_addr_family_parser, - my_addr_family_formatter, - 24 /* max_addr_bits */)); + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} +static int test_route_update_flags_protects_immutable_flags(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route *route; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags_before, flags_after, flags_at_entry; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_EXIT_ON_FALSE(insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_OUT, &route, &id) == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + flags_at_entry = WOLFSENTRY_ATOMIC_LOAD(route->flags); + WOLFSENTRY_EXIT_ON_FALSE(flags_at_entry & WOLFSENTRY_ROUTE_FLAG_IN_TABLE); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_NONE, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(route->flags) == flags_at_entry); + + /* mutable flags can still be changed. */ WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_remove_bynumber( + wolfsentry_route_update_flags( WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, + route, + WOLFSENTRY_ROUTE_FLAG_GREENLISTED, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(flags_after == (flags_at_entry | WOLFSENTRY_ROUTE_FLAG_GREENLISTED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + return 0; +} + +static int test_route_update_flags_protects_internal_flags(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route *route; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags_before, flags_after, flags_at_entry; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_EXIT_ON_FALSE(insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_OUT, &route, &id) == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + flags_at_entry = WOLFSENTRY_ATOMIC_LOAD(route->flags); + WOLFSENTRY_EXIT_ON_FALSE(flags_at_entry & WOLFSENTRY_ROUTE_FLAG_IN_TABLE); + WOLFSENTRY_EXIT_ON_TRUE(flags_at_entry & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_IN_TABLE, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_NONE, + WOLFSENTRY_ROUTE_FLAG_IN_TABLE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_NONE, + WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(route->flags) == flags_at_entry); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_install( + wolfsentry_route_delete_by_id( WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - "my_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - my_addr_family_parser, - my_addr_family_formatter, - 24 /* max_addr_bits */)); + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); - action_results = 0; + return 0; +} - /* exercise the plugins to disambiguate failures in the plugins from - * JSON-specific failures. - */ - { - byte addr_internal[3]; - wolfsentry_addr_bits_t addr_internal_len; - char addr_text[13]; - int addr_text_len; +static int test_route_table_max_purgeable_routes_shrink(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_hitcount_t max_purgeable_routes; + wolfsentry_ent_id_t ids[3]; + unsigned int i; + byte addr[4] = { 0x0a, 0x0b, 0x0c, 0x00 }; - addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); - WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_parser( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "377/377/377", - strlen("377/377/377"), - addr_internal, - &addr_internal_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == (wolfsentry_addr_bits_t)(sizeof addr_internal * 8)); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\377\377\377", sizeof addr_internal) == 0); + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 4005; + local.sa.sa_port = 4006; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(local.sa.addr, "\x0a\x0b\x0c\x14", sizeof local.addr_buf); - addr_text_len = (int)sizeof addr_text; - WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_formatter( - WOLFSENTRY_CONTEXT_ARGS_OUT, - addr_internal, - (int)(sizeof addr_internal * 8), - addr_text, - &addr_text_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_text_len == strlen("377/377/377")); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(addr_text, "377/377/377") == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); + /* three routes with no connections, all already due for purging. */ + for (i = 0; i < sizeof ids / sizeof ids[0]; ++i) { + addr[3] = (byte)(0x20 + i); + memcpy(remote.sa.addr, addr, sizeof addr); WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_parser( + wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "0/0/0", - strlen("0/0/0"), - addr_internal, - &addr_internal_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == (wolfsentry_addr_bits_t)(sizeof addr_internal * 8)); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\0\0\0", sizeof addr_internal) == 0); - - addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); - WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_parser( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "377/377/", - strlen("377/377/"), - addr_internal, - &addr_internal_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == 16); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\377\377", 2) == 0); - - addr_text_len = (int)sizeof addr_text; - WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_formatter( - WOLFSENTRY_CONTEXT_ARGS_OUT, - addr_internal, - 16, - addr_text, - &addr_text_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_text_len == strlen("377/377/")); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(addr_text, "377/377/") == 0); + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + &ids[i], &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + } + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); - addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); - WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_parser( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "377/", - strlen("377/"), - addr_internal, - &addr_internal_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == 8); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\377", 1) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); - addr_text_len = (int)sizeof addr_text; - WOLFSENTRY_EXIT_ON_FAILURE( - my_addr_family_formatter( - WOLFSENTRY_CONTEXT_ARGS_OUT, - addr_internal, - 8, - addr_text, - &addr_text_len)); - WOLFSENTRY_EXIT_ON_FALSE(addr_text_len == strlen("377/")); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(addr_text, "377/") == 0); - } + /* raising the limit, or lowering it to exactly the current count, purges nothing. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 5)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 3)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 10)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); -#ifdef WOLFSENTRY_PROTOCOL_NAMES + /* lowering the limit below the current count purges immediately. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 2)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len <= 2); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_get(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &max_purgeable_routes)); + WOLFSENTRY_EXIT_ON_FALSE(max_purgeable_routes == 2); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_remove_byname( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "my_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + return 0; +} - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_install( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - "my_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - my_addr_family_parser, - my_addr_family_formatter, - 24 /* max_addr_bits */)); +static int test_static_routes_guards(void) { + struct wolfsentry_context *wolfsentry; + static const struct wolfsentry_eventconfig config = { 0 }; - { - wolfsentry_addr_family_t family_number; + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_pton( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "my_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &family_number)); - WOLFSENTRY_EXIT_ON_FALSE(family_number == WOLFSENTRY_AF_USER_OFFSET); - - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( - ITEM_NOT_FOUND, - wolfsentry_addr_family_pton( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "no_such_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, &family_number)); - } + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - { - struct wolfsentry_addr_family_bynumber *addr_family = NULL; - const char *family_name; + WOLFSENTRY_EXIT_ON_FALSE(test_route_insert_requires_direction(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); - WOLFSENTRY_EXIT_ON_FAILURE( - (wolfsentry_addr_family_ntop( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - &addr_family, - &family_name))); - WOLFSENTRY_EXIT_ON_FALSE((family_name != NULL) && - (! strcmp(family_name,"my_AF"))); + WOLFSENTRY_EXIT_ON_FALSE(test_route_insert_by_exports_requires_direction(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, addr_family, &action_results)); - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); - } + WOLFSENTRY_EXIT_ON_FALSE(test_route_flags_sensical_validation(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); -#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_rejects_penaltybox_and_greenlist(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); - { - wolfsentry_addr_family_parser_t parser; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_get_parser( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - &parser)); - WOLFSENTRY_EXIT_ON_FALSE(parser == my_addr_family_parser); - } - { - wolfsentry_addr_family_formatter_t formatter; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_get_formatter( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - &formatter)); - WOLFSENTRY_EXIT_ON_FALSE(formatter == my_addr_family_formatter); - } + WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_protects_immutable_flags(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); -#ifdef WOLFSENTRY_PROTOCOL_NAMES - { - wolfsentry_addr_family_t family_number; - wolfsentry_addr_bits_t bits; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_pton( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "inet", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &family_number)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_max_addr_bits( - WOLFSENTRY_CONTEXT_ARGS_OUT, - family_number, - &bits)); - WOLFSENTRY_EXIT_ON_FALSE(bits == 32); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_pton( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "inet6", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &family_number)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_max_addr_bits( - WOLFSENTRY_CONTEXT_ARGS_OUT, - family_number, - &bits)); - WOLFSENTRY_EXIT_ON_FALSE(bits == 128); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_pton( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "link", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &family_number)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_max_addr_bits( - WOLFSENTRY_CONTEXT_ARGS_OUT, - family_number, - &bits)); - WOLFSENTRY_EXIT_ON_FALSE(bits == 48); + WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_protects_internal_flags(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); - /* LINK64 must roundtrip through pton → ntop → verify label. */ - { - struct wolfsentry_addr_family_bynumber *addr_family = NULL; - const char *family_name = NULL; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_pton( - WOLFSENTRY_CONTEXT_ARGS_OUT, - "LINK64", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &family_number)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_ntop( - WOLFSENTRY_CONTEXT_ARGS_OUT, - family_number, - &addr_family, - &family_name)); - WOLFSENTRY_EXIT_ON_FALSE((family_name != NULL) && (! strcmp(family_name, "LINK64"))); - if (addr_family) - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, addr_family, &action_results)); - } - } -#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + WOLFSENTRY_EXIT_ON_FALSE(test_route_table_max_purgeable_routes_shrink(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); @@ -4231,24 +5263,58 @@ static int test_user_addr_families(void) { WOLFSENTRY_RETURN_OK; } -#endif /* TEST_USER_ADDR_FAMILIES */ +#endif /* TEST_STATIC_ROUTES */ -#ifdef TEST_JSON +#ifdef TEST_DYNAMIC_RULES -#include "wolfsentry/wolfsentry_json.h" -#ifdef WOLFSENTRY_HAVE_JSON_DOM -#include -#endif +static wolfsentry_errcode_t wolfsentry_action_dummy_callback( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_context; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + WOLFSENTRY_RETURN_OK; +} + + +static int test_dynamic_rules(void) { -static int test_json(const char *fname, const char *extra_fname) { - wolfsentry_errcode_t ret; struct wolfsentry_context *wolfsentry; +#if 0 + wolfsentry_action_res_t action_results; + int n_deleted; + int ret; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } src, dst; +#endif + wolfsentry_ent_id_t id; #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS - struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; #else - struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -4262,1200 +5328,9243 @@ static int test_json(const char *fname, const char *extra_fname) { WOLFSENTRY_INIT_FLAG_NONE)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_install( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET, - "my_AF", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - my_addr_family_parser, - my_addr_family_formatter, - 24 /* max_addr_bits */)); + "connect", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_addr_family_handler_install( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_AF_USER_OFFSET + 1, - "my_AF2", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - my_addr_family_parser, - my_addr_family_formatter, - 24 /* max_addr_bits */)); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_BUILTIN_LABEL_PREFIX "connect", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id), + NOT_PERMITTED)); - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NO_ROUTES_OR_EVENTS, 1)); + /* track port scanning */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "connection_refused", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "disconnect", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "authentication_succeeded", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "negotiation_abandoned", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); { - static const char test_string[] = "hello"; - const char *value = NULL; - int value_len = -1; - struct wolfsentry_kv_pair_internal *kv_ref; + wolfsentry_ent_id_t auth_failed_id; + struct wolfsentry_event *auth_failed_event; + const struct wolfsentry_event *insertion_side_effect_demo_event; + const char *insertion_side_effect_demo_label; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_string( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - "user-string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, - &value_len, - &kv_ref)); + "authentication_failed", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(test_string)); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, test_string) == 0); + auth_failed_id = id; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_release_record( + wolfsentry_event_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - &kv_ref)); - } - - { - static const char user_cert_string[] = - "-----BEGIN CERTIFICATE-----\n" - "MIIDnzCCAyWgAwIBAgICEAEwCgYIKoZIzj0EAwMwgZcxCzAJBgNVBAYTAlVTMRMw\n" - "EQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMRAwDgYDVQQKDAd3\n" - "b2xmU1NMMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEYMBYGA1UEAwwPd3d3LndvbGZz\n" - "c2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tMCAXDTIyMDIx\n" - "NTEyNTAyNFoYDzIwNTIwMjA4MTI1MDI0WjCBlTELMAkGA1UEBhMCVVMxEzARBgNV\n" - "BAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxEDAOBgNVBAoMB0VsaXB0\n" - "aWMxEjAQBgNVBAsMCUVDQzM4NFNydjEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29t\n" - "MR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tMHYwEAYHKoZIzj0CAQYF\n" - "K4EEACIDYgAE6s+TTywJuzkUD1Zkw0C03w5jruVxSwDMBJf/4ek4lrtfkbJqzLU5\n" - "X49wWfEB9lorAWxoC89VJa9tmEgKqHTJqRegDMP70yNo/gQ8Y1CIO7lPfGc09zup\n" - "c+cbw1FeIhjso4IBQDCCATwwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAw\n" - "HQYDVR0OBBYEFII78mUv87QAxrwG/XlCdUtl0c68MIHXBgNVHSMEgc8wgcyAFKvg\n" - "wyZMGNRyu9KEjJwKBZKAElNSoYGdpIGaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UE\n" - "CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEQMA4GA1UECgwHd29sZlNT\n" - "TDEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNv\n" - "bTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbYIUaiYbTQIaM//CRxT5\n" - "51VgWi5/ESkwDgYDVR0PAQH/BAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAoG\n" - "CCqGSM49BAMDA2gAMGUCMQCEPZBU/y/EetTYGOKzLbtCN0CmHwmD3rwEeoLcVRdC\n" - "XBeqB0LcyPZQzRS3Bhk5HyQCMBNiS5/JoIzSac8WToa9nik4ROlKOmOgZjiV4n3j\n" - "F+yUIbg9aV7K5ISc2mF9G1G/0Q==\n" - "-----END CERTIFICATE-----\n"; - const char *value = NULL; - int value_len = -1; - struct wolfsentry_kv_pair_internal *kv_ref; + "insertion_side_effect_demo", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_string( + wolfsentry_event_set_aux_event( WOLFSENTRY_CONTEXT_ARGS_OUT, - "user-cert-string", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, - &value_len, - &kv_ref)); - - WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(user_cert_string)); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, user_cert_string) == 0); + "authentication_failed", + -1, + "insertion_side_effect_demo", + -1)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_release_record( + wolfsentry_event_get_reference( WOLFSENTRY_CONTEXT_ARGS_OUT, - &kv_ref)); - } + "authentication_failed", + -1, + &auth_failed_event)); - WOLFSENTRY_EXIT_ON_FAILURE(load_test_action_handlers(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(auth_failed_id == wolfsentry_get_object_id(auth_failed_event)); - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_DRY_RUN, 1)); + WOLFSENTRY_EXIT_ON_FALSE( + insertion_side_effect_demo_event = wolfsentry_event_get_aux_event(auth_failed_event) + ); - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, 1)); + WOLFSENTRY_EXIT_ON_FALSE( + insertion_side_effect_demo_label = wolfsentry_event_get_label(insertion_side_effect_demo_event) + ); - WOLFSENTRY_EXIT_ON_SUCCESS(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, 0)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(insertion_side_effect_demo_label, "insertion_side_effect_demo") == 0); - { - static const char *bad_json[] = { - "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"user-string\" : \"should collide\" } }", - "{ \"wolfsentry-config-version\" : 2, \"user-values\" : { \"ok-user-string\" : \"shouldn't collide\" } }", - "{ \"wolfsentry-config-version\" : 1, \"not-user-values\" : { \"ok-user-string\" : \"shouldn't collide\" } }", - "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"too-long-user-string-123456789-abcdefghi\" : \"x\" } }", - }; - const char **bad_json_i; - for (bad_json_i = bad_json; bad_json_i < &bad_json[length_of_array(bad_json)]; ++bad_json_i) { - WOLFSENTRY_EXIT_ON_SUCCESS( - wolfsentry_config_json_oneshot( - WOLFSENTRY_CONTEXT_ARGS_OUT, - (const unsigned char *)*bad_json_i, - strlen(*bad_json_i), - WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, - NULL, - 0)); - } + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + auth_failed_event, + NULL /* action_results */)); } - { - struct wolfsentry_route_table *main_routes, main_routes_copy; - const char *value = NULL; - int value_len = -1; - struct wolfsentry_kv_pair_internal *kv_ref; - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } remote, local; - wolfsentry_ent_id_t route_id; - wolfsentry_route_flags_t inexact_matches; - wolfsentry_action_res_t action_results; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "deletion_side_effect_demo", + -1 /* label_len */, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); - main_routes_copy = *main_routes; + { + struct wolfsentry_eventconfig eventconfig; - if (extra_fname) { - remote.sa.sa_family = local.sa.sa_family = AF_INET; - remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; - remote.sa.sa_port = 12345; - local.sa.sa_port = 13579; - remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - remote.sa.interface = local.sa.interface = 1; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "deletion_side_effect_demo", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); - memcpy(remote.sa.addr,"\12\24\36\50",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\62\74\106\120",sizeof local.addr_buf); + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config2.route_private_data_size) && + (eventconfig.route_private_data_alignment == config2.route_private_data_alignment) && + (eventconfig.max_connection_count == config2.max_connection_count)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - NULL /* event_label */, - 0 /* event_label_len */, - NULL /* caller_arg */, - &route_id, - &inexact_matches, - &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(route_id == WOLFSENTRY_ENT_ID_NONE); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_FALLTHROUGH)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "deletion_side_effect_demo", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, extra_fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, 1)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "deletion_side_effect_demo", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - NULL /* event_label */, - 0 /* event_label_len */, - NULL /* caller_arg */, - &route_id, - &inexact_matches, - &action_results)); - WOLFSENTRY_EXIT_ON_TRUE(route_id == WOLFSENTRY_ENT_ID_NONE); - WOLFSENTRY_EXIT_ON_FALSE(action_results == WOLFSENTRY_ACTION_RES_ACCEPT); - - memcpy(remote.sa.addr,"\13\24\36\50",sizeof remote.addr_buf); - - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - NULL /* event_label */, - 0 /* event_label_len */, - NULL /* caller_arg */, - &route_id, - &inexact_matches, - &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(route_id == WOLFSENTRY_ENT_ID_NONE); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_FALLTHROUGH)); - } else { - static const char *trivial_test_json = "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"extra-user-string\" : \"extra hello\" } }"; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_config_json_oneshot( - WOLFSENTRY_CONTEXT_ARGS_OUT, - (const unsigned char *)trivial_test_json, - strlen(trivial_test_json), - WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, - NULL, - 0)); - } - - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); - - WOLFSENTRY_EXIT_ON_FALSE((main_routes->max_purgeable_routes == main_routes_copy.max_purgeable_routes) && - (main_routes->default_policy == main_routes_copy.default_policy)); - - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config.route_private_data_size) && + (eventconfig.route_private_data_alignment == config.route_private_data_alignment) && + (eventconfig.max_connection_count == config.max_connection_count)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_string( + wolfsentry_event_delete( WOLFSENTRY_CONTEXT_ARGS_OUT, - "user-string", + "deletion_side_effect_demo", WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, - &value_len, - &kv_ref)); - - WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen("hello")); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, "hello") == 0); - - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_release_record( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &kv_ref)); - + NULL /* action_results */)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_get_string( + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_event_delete( WOLFSENTRY_CONTEXT_ARGS_OUT, - "extra-user-string", + "deletion_side_effect_demo", WOLFSENTRY_LENGTH_NULL_TERMINATED, - &value, - &value_len, - &kv_ref)); + NULL /* action_results */)); + } - WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen("extra hello")); - WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, "extra hello") == 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "insert_always", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_user_value_release_record( + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - &kv_ref)); - } + WOLFSENTRY_BUILTIN_LABEL_PREFIX "insert_always", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id), + NOT_PERMITTED)); - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, 1)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "insert_alway", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); { - struct wolfsentry_route_table *main_routes, main_routes_copy; - struct wolfsentry_cursor *cursor; - WOLFSENTRY_BYTE_STREAM_DECLARE_STACK(json_out, 16384); - WOLFSENTRY_BYTE_STREAM_DECLARE_HEAP(json_out2, 16384); - wolfsentry_hitcount_t n_seen = 0; - char err_buf[512]; - - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_BYTE_STREAM_INIT_HEAP(json_out2) != NULL); + static char too_long_label[WOLFSENTRY_MAX_LABEL_BYTES + 2]; + memset(too_long_label, 'x', sizeof too_long_label - 1); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + too_long_label[sizeof too_long_label - 1] = 0; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + too_long_label, + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); - WOLFSENTRY_BYTE_STREAM_RESET(json_out); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + too_long_label, + sizeof too_long_label - 1, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_table_dump_json_start( + wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out), - WOLFSENTRY_BYTE_STREAM_SPC(json_out), - WOLFSENTRY_FORMAT_FLAG_NONE)); + too_long_label, + sizeof too_long_label - 2, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); - for (;;) { - ret = wolfsentry_route_table_dump_json_next( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out), - WOLFSENTRY_BYTE_STREAM_SPC(json_out), - WOLFSENTRY_FORMAT_FLAG_NONE); - if (ret < 0) { - if (! WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) - WOLFSENTRY_EXIT_ON_FAILURE(ret); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_table_dump_json_end( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out), - WOLFSENTRY_BYTE_STREAM_SPC(json_out), - WOLFSENTRY_FORMAT_FLAG_NONE)); - } else - ++n_seen; - if (ret < 0) - break; - } + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + too_long_label, + sizeof too_long_label - 1, + NULL /* action_results */)); - WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->routes->header.n_ents); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + too_long_label, + -1, + NULL /* action_results */)); - main_routes_copy = *main_routes; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL, + -1, + NULL /* action_results */)); - ret = wolfsentry_config_json_oneshot( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_BYTE_STREAM_HEAD(json_out), - WOLFSENTRY_BYTE_STREAM_LEN(json_out), - WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT | WOLFSENTRY_CONFIG_LOAD_FLAG_FLUSH_ONLY_ROUTES, - err_buf, - sizeof err_buf); - if (ret < 0) { - (void)fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); - WOLFSENTRY_EXIT_ON_FAILURE(ret); - } + too_long_label[sizeof too_long_label - 2] = 0; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_ALREADY_PRESENT, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + too_long_label, + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); - WOLFSENTRY_EXIT_ON_FALSE((main_routes->max_purgeable_routes == main_routes_copy.max_purgeable_routes) && - (main_routes->default_policy == main_routes_copy.default_policy)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL, + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); - WOLFSENTRY_BYTE_STREAM_RESET(json_out2); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + too_long_label, + 0 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_table_dump_json_start( + wolfsentry_action_delete( WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &cursor, + too_long_label, + -1 /* label_len */, + NULL /* action_results */)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "set_connect_wildcards", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "set_connectionreset_wildcards", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "increment_derogatory", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "increment_commendable", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "check_counts", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + { + struct wolfsentry_action *action; + wolfsentry_action_flags_t flags; + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "checXXXounts", + -1 /* label_len */, + &action)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "checXXXounts", + 0 /* label_len */, + &action)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "checXXXounts", + WOLFSENTRY_MAX_LABEL_BYTES + 1 /* label_len */, + &action)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "check_counts", + -1 /* label_len */, + &action)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_flags( + action, + &flags)); + WOLFSENTRY_EXIT_ON_FALSE(flags == WOLFSENTRY_ACTION_FLAG_NONE); + + { + wolfsentry_action_flags_t flags_before, flags_after; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_update_flags( + action, + WOLFSENTRY_ACTION_FLAG_DISABLED, + WOLFSENTRY_ACTION_FLAG_NONE, + &flags_before, + &flags_after)); + WOLFSENTRY_EXIT_ON_FALSE(flags_before == WOLFSENTRY_ACTION_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FALSE(flags_after == WOLFSENTRY_ACTION_FLAG_DISABLED); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_flags( + action, + &flags)); + WOLFSENTRY_EXIT_ON_FALSE(flags == WOLFSENTRY_ACTION_FLAG_DISABLED); + + { + wolfsentry_action_flags_t flags_before, flags_after; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_update_flags( + action, + WOLFSENTRY_ACTION_FLAG_NONE, + WOLFSENTRY_ACTION_FLAG_DISABLED, + &flags_before, + &flags_after)); + WOLFSENTRY_EXIT_ON_FALSE(flags_before == WOLFSENTRY_ACTION_FLAG_DISABLED); + WOLFSENTRY_EXIT_ON_FALSE(flags_after == WOLFSENTRY_ACTION_FLAG_NONE); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_flags( + action, + &flags)); + WOLFSENTRY_EXIT_ON_FALSE(flags == WOLFSENTRY_ACTION_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action, NULL)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "add_to_greenlist", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "del_from_greenlist", + -1 /* label_len */, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "del_from_greenlist", + -1)); + + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_event_action_prepend( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "del_from_greenlist", + -1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_prepend( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "add_to_greenlist", + -1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_insert_after( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "check_counts", + -1, + "add_to_greenlist", + -1)); + + { + struct wolfsentry_action_list_ent *cursor; + const char *action_label; + int action_label_len; + static const char *labels[] = { "add_to_greenlist", "check_counts", "del_from_greenlist" }; + int label_i = 0; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_list_start( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + &cursor)); + + while (wolfsentry_event_action_list_next( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &cursor, + &action_label, + &action_label_len) >= 0) { + WOLFSENTRY_EXIT_ON_TRUE((size_t)label_i >= sizeof labels / sizeof labels[0]); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(action_label, labels[label_i++]) == 0); + } + + WOLFSENTRY_EXIT_ON_FALSE(label_i == sizeof labels / sizeof labels[0]); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_list_done( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &cursor)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "match_side_effect_demo", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "del_from_greenlist", + -1)); + + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t action_dispatch_counter_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + if (handler_arg != NULL) + ++*(int *)handler_arg; + WOLFSENTRY_RETURN_OK; +} + +static void dynamic_rules_fill_sockaddrs( + struct wolfsentry_sockaddr *remote, + struct wolfsentry_sockaddr *local, + size_t addr_bytes) +{ + remote->sa_family = local->sa_family = AF_INET; + remote->sa_proto = local->sa_proto = IPPROTO_TCP; + remote->sa_port = 12345; + local->sa_port = 443; + remote->addr_len = local->addr_len = (wolfsentry_addr_bits_t)(addr_bytes * BITS_PER_BYTE); + remote->interface = local->interface = 1; + memcpy(remote->addr, "\1\2\3\4", addr_bytes); + memcpy(local->addr, "\4\3\2\1", addr_bytes); +} + +static int test_dispatch_inhibited_insert_actions(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *route = NULL; + wolfsentry_route_flags_t route_flags; + int insert_calls = 0; + int delete_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &insert_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + "test-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test-delete-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &delete_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_DELETE, + "test-delete-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_inhibit_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_and_check_out( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &route, + &action_results)); + + /* actions were inhibited at insert time, so the handler must not have run, + * and the route must not be marked as having run its insert actions. + */ + WOLFSENTRY_EXIT_ON_FALSE(insert_calls == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)); + + /* re-enable actions and run the deferred insert actions; the handler must + * now run exactly once for the previously inhibited route. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_enable_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_insert_actions(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(insert_calls == 1); + + /* with actions inhibited again, deleting the route must not run its delete + * handler nor mark the route as having run its delete actions. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_inhibit_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route), + NULL /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(delete_calls == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +struct reentrant_delete_ctx { + const char *event_label; + const char *action_label; + wolfsentry_action_type_t which_action_list; + int call_count; + wolfsentry_errcode_t delete_ret; +}; + +static wolfsentry_errcode_t action_reentrant_delete_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + struct reentrant_delete_ctx *c = (struct reentrant_delete_ctx *)handler_arg; + + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + if (c != NULL) { + ++c->call_count; + c->delete_ret = wolfsentry_event_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + c->event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + c->which_action_list, + c->action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED); + } + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_reentrant_action_delete(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct reentrant_delete_ctx sdctx; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&sdctx, 0, sizeof sdctx); + sdctx.event_label = "ev-match"; + sdctx.action_label = "self-del-action"; + sdctx.which_action_list = WOLFSENTRY_ACTION_TYPE_MATCH; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self-del-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_reentrant_delete_handler, + &sdctx, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "after-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &after_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* a self-deleting action followed by another action in the same list: the + * dispatch loop must survive the first handler deleting its own entry and + * still reach the second action. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "self-del-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "after-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + + /* a handler's attempt to delete its own live entry is refused, and + * iteration still reaches the following action. + */ + WOLFSENTRY_EXIT_ON_FALSE(sdctx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(sdctx.delete_ret, BUSY)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + + /* now the first handler targets the SUCCESSOR entry: that delete must also + * be refused, and the successor must still run. + */ + sdctx.call_count = 0; + sdctx.action_label = "after-action"; + after_calls = 0; + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(sdctx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(sdctx.delete_ret, BUSY)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +struct event_del_ctx { + const char *event_label; + int call_count; + wolfsentry_errcode_t del_ret; +}; + +static wolfsentry_errcode_t action_delete_own_event_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + struct event_del_ctx *c = (struct event_del_ctx *)handler_arg; + + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + if (c != NULL) { + ++c->call_count; + c->del_ret = wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + c->event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */); + } + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t action_error_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + if (handler_arg != NULL) + ++*(int *)handler_arg; + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); +} + +static int test_dispatch_handler_deletes_own_event(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct event_del_ctx edctx; + struct wolfsentry_event *gone_ref = NULL; + int after_calls = 0; + int probe_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&edctx, 0, sizeof edctx); + edctx.event_label = "ev-selfev"; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "evdel-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_delete_own_event_handler, &edctx, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "after-ev-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "probe-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &probe_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "evdel-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-ev-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* a separate event used only to prove the dispatch guard is released. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-probe", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-probe", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "probe-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the handler deleted its own event mid-dispatch; the delete succeeded and + * the event is gone from the table, yet the held event reference kept the + * list valid so iteration reached the following action. + */ + WOLFSENTRY_EXIT_ON_FALSE(edctx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_IS_SUCCESS(edctx.del_ret)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", + WOLFSENTRY_LENGTH_NULL_TERMINATED, &gone_ref), + ITEM_NOT_FOUND)); + + /* the dispatch released its guard, so an unrelated list is now mutable. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-probe", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "probe-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_handler_error(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + int err_calls = 0; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_error_handler, &err_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "after-err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the first handler returned an error, taking the loop's error-cleanup + * exit, so the following action did not run. + */ + WOLFSENTRY_EXIT_ON_FALSE(err_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 0); + + /* the error exit still released the dispatch guard, so the list is mutable. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +struct ctx_exchange_ctx { + struct wolfsentry_context *peer; + int call_count; + wolfsentry_errcode_t exchange_ret; +}; + +static wolfsentry_errcode_t action_context_exchange_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + struct ctx_exchange_ctx *c = (struct ctx_exchange_ctx *)handler_arg; + + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + if (c != NULL) { + ++c->call_count; + c->exchange_ret = wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, c->peer); + } + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_context_exchange_during_dispatch(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *peer = NULL; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct ctx_exchange_ctx cectx; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&cectx, 0, sizeof cectx); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_context_exchange_handler, &cectx, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "after-exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + /* a peer context to attempt to swap in from the handler. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &peer, WOLFSENTRY_CLONE_FLAG_NONE)); + cectx.peer = peer; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* a context exchange attempted mid-dispatch must be refused, and the + * following action must still run. + */ + WOLFSENTRY_EXIT_ON_FALSE(cectx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(cectx.exchange_ret, BUSY)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&peer))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_inhibit_actions_flag(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + int match_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "inh-match-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &match_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-inh", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-inh", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "inh-match-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "ev-inh", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + /* with actions inhibited, the matched route's handler must not fire. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_inhibit_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(match_calls == 0); + + /* with actions re-enabled, the same dispatch must fire the handler. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_enable_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(match_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_action_drop_reference_clears_results(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_action *action = NULL; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "drop-ref-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + NULL, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "drop-ref-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &action)); + + /* with a reference still held, a non-final drop must clear stale caller + * bits rather than leaving them, matching the event and route drop APIs. + */ + action_results = WOLFSENTRY_ACTION_RES_DEALLOCATED | WOLFSENTRY_ACTION_RES_REJECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(action_results == WOLFSENTRY_ACTION_RES_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t action_set_stop_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + if (handler_arg != NULL) + ++*(int *)handler_arg; + WOLFSENTRY_SET_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_action_stop(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + int stop_calls = 0; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_set_stop_handler, &stop_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "after-stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the first match action signals stop, so the second must be skipped. */ + WOLFSENTRY_EXIT_ON_FALSE(stop_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_disabled_action(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct wolfsentry_action *disabled_action = NULL; + wolfsentry_action_flags_t flags_before, flags_after; + int disabled_calls = 0; + int enabled_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "disabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &disabled_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "enabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &enabled_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "disabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "enabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, "disabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, &disabled_action)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_update_flags( + disabled_action, WOLFSENTRY_ACTION_FLAG_DISABLED, WOLFSENTRY_ACTION_FLAG_NONE, &flags_before, &flags_after)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the disabled action must be skipped while the enabled one fires. */ + WOLFSENTRY_EXIT_ON_FALSE(disabled_calls == 0); + WOLFSENTRY_EXIT_ON_FALSE(enabled_calls == 1); + + /* clearing the disabled flag must let the action run again. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_update_flags( + disabled_action, WOLFSENTRY_ACTION_FLAG_NONE, WOLFSENTRY_ACTION_FLAG_DISABLED, &flags_before, &flags_after)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(disabled_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(enabled_calls == 2); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, disabled_action, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_dont_count_hits(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct wolfsentry_action *hc_action = NULL; + int hc_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t counted_flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + wolfsentry_route_flags_t uncounted_flags = counted_flags | WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_HITS; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "hc-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &hc_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "hc-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, "hc-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, &hc_action)); + + /* a route with DONT_COUNT_HITS: its matched action's handler runs but its + * hitcount must not advance. + */ + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + uncounted_flags, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, uncounted_flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(hc_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(hc_action->header.hitcount == 0); + + /* a route without the flag advances the hitcount on dispatch. */ + memcpy(remote.sa.addr, "\5\6\7\10", sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + counted_flags, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, counted_flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(hc_calls == 2); + WOLFSENTRY_EXIT_ON_FALSE(hc_action->header.hitcount == 1); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, hc_action, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_insert_delete_already_guard(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *route = NULL; + wolfsentry_route_flags_t route_flags; + int ins_calls = 0; + int del_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ins-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &ins_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "del-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &del_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, "ins-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_DELETE, "del-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_and_check_out( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, &route, &action_results)); + + /* insert actions run once and the route is marked accordingly. */ + WOLFSENTRY_EXIT_ON_FALSE(ins_calls == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)); + + /* a repeated insert pass must not re-run them while the flag is set. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_insert_actions(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(ins_calls == 1); + + /* clearing the status reopens the insert actions for that route. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_clear_insert_action_status(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_insert_actions(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(ins_calls == 2); + + /* deleting the route runs its delete actions exactly once and marks the + * route so the delete-side guard would reject a repeat dispatch. + */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route), + NULL /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(del_calls == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static wolfsentry_errcode_t test_regression_bitmask_match_specificity(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id, broad_id, narrow_id; + struct wolfsentry_route_exports broad_route, narrow_route; + byte broad_addr[8], narrow_addr[8]; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + static const wolfsentry_route_flags_t common_flags = + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&broad_route, 0, sizeof broad_route); + memset(&narrow_route, 0, sizeof narrow_route); + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + + /* broad bitmask route 10.0.0.0/255.0.0.0, greenlisted (accept). */ + memcpy(broad_addr, "\x0A\x00\x00\x00\xFF\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + broad_route.sa_family = AF_INET; + broad_route.sa_proto = IPPROTO_TCP; + broad_route.remote_address = broad_addr; + broad_route.remote.addr_len = 64; + broad_route.flags = common_flags | WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &broad_route, + &broad_id, &action_results)); + + /* narrow bitmask route 10.1.2.0/255.255.255.0, penaltyboxed (reject). */ + memcpy(narrow_addr, "\x0A\x01\x02\x00\xFF\xFF\xFF\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + narrow_route.sa_family = AF_INET; + narrow_route.sa_proto = IPPROTO_TCP; + narrow_route.remote_address = narrow_addr; + narrow_route.remote.addr_len = 64; + narrow_route.flags = common_flags | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &narrow_route, + &narrow_id, &action_results)); + + (void)broad_id; + + /* 10.1.2.3 matches both; the more specific /24 route must win, so the + * verdict is reject, not accept. + */ + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x01\x02\x03", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + /* non-nested masks: 192.0.0.1 inexactly matches a two-bit mask whose + * address is closer to it and a three-bit mask whose address is not; the + * three-bit mask constrains more bits, so it must win. + */ + memcpy(broad_addr, "\xC0\x00\x00\x00\xC0\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &broad_route, + &broad_id, &action_results)); + memcpy(narrow_addr, "\x00\x00\x00\x00\x38\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &narrow_route, + &narrow_id, &action_results)); + + memcpy(remote.sa.addr, "\xC0\x00\x00\x01", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + +/* a stale purge either purged something or had nothing due */ +static int regression_purge_ok(wolfsentry_errcode_t ret) { + return WOLFSENTRY_IS_SUCCESS(ret) || WOLFSENTRY_ERROR_CODE_IS(ret, ALREADY); +} + +static wolfsentry_errcode_t test_regression_stale_purge_connected_idle(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, route_flags; + wolfsentry_ent_id_t id, id_b, route_id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, remote_b, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x09\x08\x07", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* a second route with no connection, less stale than the first; it sits + * ahead of the protected route in the purge list and must still be purged. */ + memcpy(&remote_b, &remote, sizeof remote_b); + memcpy(remote_b.sa.addr, "\x0A\x09\x08\x06", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_b.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_b, &action_results)); + + /* idle-time limit far larger than this test's wall-clock runtime, so a + * connected route is within the limit and must be preserved by a purge. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* open a connection on the route (connection_count -> 1). */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_CONNECT); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + + /* mark the protected route as the most stale entry (purge_after 1), and the + * unconnected route as less stale (purge_after 2); both are in the past. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id_b, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 2)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* the route's live connection is within the idle limit, so it must survive. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + /* the unconnected route behind it must not be stranded by the purge. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id_b, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_dispatch_error_not_masked(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, flags; + wolfsentry_ent_id_t id; + wolfsentry_errcode_t ret; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.addr_len = 0; + local.sa.sa_family = AF_INET; + local.sa.addr_len = 0; + + /* a family wildcard without the remote/local/proto wildcards it requires is + * rejected while the ephemeral target route is built, before any rule is + * found; the dispatcher must surface that error, not report a successful + * default-policy fallthrough. + */ + WOLFSENTRY_CLEAR_ALL_BITS(flags); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + ret = wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_IS_FAILURE(ret)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ERROR)); + /* the default policy is unset (NONE) here, so the failure must fail closed + * with an explicit reject rather than leaving the packet to be allowed. */ + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *ctx_clone = NULL; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t route_flags; + wolfsentry_ent_id_t id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x05\x06\x07", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* clone before the route has a purge time, so the clone's copy of the route + * is not in the clone's purge list. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + /* give the live route a (stale) purge time, so copy_metadata will propagate + * a nonzero purge_after into the clone's not-yet-listed route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* swap the clone in, copying the live metadata across. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, ctx_clone)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + /* the swapped-in route now has a stale purge_after, so it must be reachable + * from the purge list and get purged. */ + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_route_api_invariants(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route *route; + const struct wolfsentry_route *fallthrough_route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, route_flags; + wolfsentry_ent_id_t id; + char default_event_label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + int default_event_label_len; + int new_count; + char addr_fmt_buf[32]; + int addr_fmt_len; + byte link_addr[2]; + size_t i; + static const wolfsentry_action_res_t forbidden_inited[] = { + WOLFSENTRY_ACTION_RES_ACCEPT, + WOLFSENTRY_ACTION_RES_REJECT, + WOLFSENTRY_ACTION_RES_STOP, + WOLFSENTRY_ACTION_RES_DEALLOCATED, + WOLFSENTRY_ACTION_RES_ERROR + }; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x02\x03\x04", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + /* internal-only result bits must not be accepted as caller-initialized + * input to the dispatch-with-inited-result entry points. */ + action_results = WOLFSENTRY_ACTION_RES_FALLTHROUGH; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + + action_results = WOLFSENTRY_ACTION_RES_INSERTED; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + + /* a default policy may be accept or reject, never both at once. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT), + INVALID_ARG)); + + /* reading back the default event must report the written label length, not + * leave the caller's in/out length untouched. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "default_evt", -1 /* label_len */, 10 /* priority */, + NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_set_default_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, "default_evt", -1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + default_event_label_len = (int)sizeof default_event_label; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_get_default_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, default_event_label, &default_event_label_len)); + WOLFSENTRY_EXIT_ON_FALSE(default_event_label_len == (int)strlen("default_evt")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(default_event_label, "default_evt") == 0); + + /* incrementing a derogatory count by zero is a no-op that must succeed even + * when the count is saturated, not be mistaken for an averted overflow. */ + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + new_count = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_increment_derogatory_count(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 65535, &new_count)); + WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); + new_count = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_increment_derogatory_count(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 0, &new_count)); + WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* the fallthrough route belongs to no table, so setting a purge time on it + * must be refused rather than dereferencing a null parent table. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT, table, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, 1), + NOT_PERMITTED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* a link address whose length is not a whole number of bytes must render + * its final partial byte, not silently drop it. */ + link_addr[0] = 0xAB; + link_addr[1] = 0xCD; + addr_fmt_len = (int)sizeof addr_fmt_buf; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_format_address( + WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_AF_LINK, link_addr, 12 /* addr_bits */, + addr_fmt_buf, &addr_fmt_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_len == 5); + WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_buf[2] == ':'); + + /* a null action_results must be rejected up front, not dereferenced while + * clearing it. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, NULL /* action_results */), + INVALID_ARG)); + + /* a "reset" default policy (reject plus port reset) is accepted and carried + * into unmatched verdicts; a port reset cannot qualify an accept. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_PORT_RESET), + INVALID_ARG)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + memcpy(remote.sa.addr, "\x0A\x63\x63\x63", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_SUCCESS_CODE_IS( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + USED_FALLBACK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + + /* a caller may not pre-decide the verdict or pre-set internal status bits. */ + for (i = 0; i < sizeof forbidden_inited / sizeof forbidden_inited[0]; ++i) { + action_results = forbidden_inited[i]; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +/* insert an inbound TCP/IPv4 route with wildcarded local side and ports; a NULL + * remote_addr leaves the remote address empty (pair with the address wildcard). */ +static wolfsentry_errcode_t regression_insert( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *remote_addr, + unsigned int remote_prefix_bits, + const char *event_label, + wolfsentry_route_flags_t extra_flags, + wolfsentry_ent_id_t *id) +{ + wolfsentry_action_res_t action_results; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + if (remote_addr != NULL) { + remote.sa.addr_len = (wolfsentry_addr_bits_t)remote_prefix_bits; + memcpy(remote.sa.addr, remote_addr, sizeof remote.addr_buf); + } + + return wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + extra_flags + | WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD, + event_label, event_label ? -1 : 0, + id, &action_results); +} + +/* dispatch an inbound TCP/IPv4 packet from a 4-byte remote address, with the + * caller-initialized result bits in inited_results. */ +static wolfsentry_errcode_t regression_dispatch( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *remote_addr, + wolfsentry_action_res_t inited_results, + wolfsentry_ent_id_t *id, + wolfsentry_action_res_t *action_results) +{ + wolfsentry_route_flags_t inexact_matches; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, remote_addr, sizeof remote.addr_buf); + *action_results = inited_results; + + return wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + id, &inexact_matches, action_results); +} + +static wolfsentry_time_t regression_fake_now; + +static wolfsentry_errcode_t regression_fake_get_time(void *context, wolfsentry_time_t *now) { + (void)context; + *now = regression_fake_now; + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t regression_route_meta( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t id, + struct wolfsentry_route_metadata_exports *meta) +{ + struct wolfsentry_route *route; + wolfsentry_errcode_t ret, unlock_ret; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret < 0) + return ret; + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route); + if (ret >= 0) + ret = wolfsentry_route_get_metadata(route, meta); + unlock_ret = wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret >= 0) + ret = unlock_ret; + return ret; +} + +static wolfsentry_errcode_t regression_set_purge_time( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t id, + wolfsentry_time_t purge_after) +{ + struct wolfsentry_route *route; + wolfsentry_errcode_t ret, unlock_ret; + + ret = wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret < 0) + return ret; + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route); + if (ret >= 0) + ret = wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, purge_after); + unlock_ret = wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret >= 0) + ret = unlock_ret; + return ret; +} + +static wolfsentry_errcode_t regression_insert_pbox_event( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *label, + wolfsentry_hitcount_t derog_threshold, + wolfsentry_time_t penaltybox_duration, + wolfsentry_eventconfig_flags_t flags) +{ + struct wolfsentry_eventconfig event_config; + + memset(&event_config, 0, sizeof event_config); + event_config.route_private_data_size = PRIVATE_DATA_SIZE; + event_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + event_config.derogatory_threshold_for_penaltybox = derog_threshold; + event_config.penaltybox_duration = penaltybox_duration; + event_config.flags = flags; + return wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, label, -1, 10, &event_config, WOLFSENTRY_EVENT_FLAG_NONE, NULL /* id */); +} + +static wolfsentry_errcode_t test_regression_route_timing(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + unsigned int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* drive the context clock by hand so time boundaries are exact. */ + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + /* a route expiring one tick from now survives a purge, and is purged once + * the clock reaches its purge time. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x01", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now + 1)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + + /* an expired route with a live connection idle for exactly the limit is + * kept; one tick longer and it is purged. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, 100)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x02", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now)); + regression_fake_now += 100; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + + /* a route penalty-boxed by its second derogatory event stays boxed at + * exactly the box duration, and is released one tick later. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "pbox", 2, 50, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", 32, "pbox", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + regression_fake_now += 50; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + /* a zero derogatory threshold disables penalty boxing entirely. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "nothresh", 0, 50, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x04", 32, "nothresh", WOLFSENTRY_ROUTE_FLAG_GREENLISTED, &id)); + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x04", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + + /* a zero box duration is permanent, and a route boxed at insert (no box + * time recorded) is never released by the timeout. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "pboxperm", 2, 0, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", 32, "pboxperm", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", 32, "pbox", WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, &id)); + for (i = 0; i < 2; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + regression_fake_now += 1000000; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + /* with commendable events ignored, reaching the derogatory threshold boxes + * the route even though commendable events would otherwise offset it. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "ignorecommend", 2, 0, WOLFSENTRY_EVENTCONFIG_FLAG_DEROGATORY_THRESHOLD_IGNORE_COMMENDABLE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", 32, "ignorecommend", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 2; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_COMMENDABLE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_connection_limits(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_eventconfig limit_config; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + unsigned int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&limit_config, 0, sizeof limit_config); + limit_config.route_private_data_size = PRIVATE_DATA_SIZE; + limit_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + limit_config.max_connection_count = 2; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limit2", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + + /* a limit the 16-bit per-route counter cannot enforce is refused rather + * than silently capped, and the counter maximum is enforced exactly. */ + limit_config.max_connection_count = 0x10000U; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limitmax", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + limit_config.max_connection_count = 0xffffU; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limitmax", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", 32, "limitmax", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 0xffffU; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0xffffU); + limit_config.max_connection_count = 2; + + /* a route that opts out of connection counting is never limited. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS, &id)); + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + + /* a counted route admits exactly the limit, rejects the next connection + * without counting it, and reopens a slot after a disconnect. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 2; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 2); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 2); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_connection_count_zero_limit(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_eventconfig limit_config; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&limit_config, 0, sizeof limit_config); + limit_config.route_private_data_size = PRIVATE_DATA_SIZE; + limit_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + limit_config.max_connection_count = 2; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "zerolimit", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", 32, "zerolimit", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + + /* a connection opened under a limit stays counted after the limit is + * removed, and connections opened afterward are counted too. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + limit_config.max_connection_count = 0; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_update_config(WOLFSENTRY_CONTEXT_ARGS_OUT, "zerolimit", -1, &limit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_purge_list_reinsert(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t ids[3], matched_id; + static const char * const addrs[3] = { "\x0A\x08\x0A\x01", "\x0A\x08\x0A\x02", "\x0A\x08\x0A\x03" }; + int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* three expired routes, each protected by a live connection */ + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, addrs[i], 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &ids[i])); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, addrs[i], WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); + } + /* moving the stalest entry to the head must not leave a stale back link, + * so a scan that visits every protected entry still terminates. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[0], 40)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + for (i = 0; i < 3; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +/* intervals in milliseconds, unlike the microsecond absolute clock. */ +static wolfsentry_errcode_t regression_ms_interval_from_seconds(time_t howlong_secs, long howlong_nsecs, wolfsentry_time_t *howlong) { + *howlong = ((wolfsentry_time_t)howlong_secs * 1000) + (howlong_nsecs / 1000000); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t ids[9], matched_id; + byte addr[4] = { 0x0A, 0x08, 0x0B, 0x00 }; + int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + wolfsentry_get_timecbs(wolfsentry)->interval_from_seconds = regression_ms_interval_from_seconds; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* eight expired connected routes at the stale end, and one expired + * unconnected route queued behind them. */ + for (i = 0; i < 9; ++i) { + addr[3] = (byte)(i + 1); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, (const char *)addr, 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &ids[i])); + if (i < 8) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, (const char *)addr, WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + } + for (i = 0; i < 9; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); + + for (i = 0; i < 3; ++i) + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge_one_opportunistically(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[8], &meta)); + /* the connected routes are requeued one purge margin out, as an interval */ + for (i = 0; i < 8; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(meta.purge_after == regression_fake_now + (WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS * 1000)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_purge_idle_edges(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_route_exports exports; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + byte addr[4] = { 0x0A, 0x08, 0x0C, 0x01 }; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, 100)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* an imported route carrying a live connection but never hit is idle + * since its insertion, not since the epoch. */ + memset(&exports, 0, sizeof exports); + exports.sa_family = AF_INET; + exports.sa_proto = IPPROTO_TCP; + exports.remote_address = addr; + exports.remote.addr_len = 32; + exports.flags = WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + exports.meta.connection_count = 1; + exports.meta.purge_after = regression_fake_now + 1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_by_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &exports, &id, &action_results)); + regression_fake_now += 2; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* trigger_label */, 0, &action_results)); + + /* a negative idle limit, like zero, never purges a connected route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, -1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x0C\x02", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x0C\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now)); + ++regression_fake_now; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_regression_prefix_specificity(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t broad_id, narrow_id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* equal-priority overlapping prefixes: the longer match must win inside + * the /25, and the /24 must still match outside it. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x00", 24, NULL, WOLFSENTRY_ROUTE_FLAG_GREENLISTED, &broad_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x80", 25, NULL, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, &narrow_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\xC8", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(matched_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x0A", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(matched_id == broad_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_dynamic_rules_regressions(void) { +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); +#endif + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_stale_purge_connected_idle(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_dispatch_error_not_masked(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_copy_metadata_purge_list(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_count_zero_limit(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_list_reinsert(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bounded_purge_progress(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_idle_edges(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t test_dynamic_count_main_routes( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_hitcount_t *n_routes) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; + + *n_routes = 0; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + + ret = wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes); + if (ret >= 0) { + ret = wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + if (ret >= 0) { + for (ret = wolfsentry_route_table_iterate_current(main_routes, cursor, &route); + ret >= 0; + ret = wolfsentry_route_table_iterate_next(main_routes, cursor, &route)) + { + ++*n_routes; + } + ret = wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + } + } + + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_ERROR_RERETURN(ret); +} + +static wolfsentry_errcode_t test_track_peer_sentinel_action( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + ++*(int *)handler_context; + + WOLFSENTRY_RETURN_OK; +} + +static int test_track_peer_ephemeral_route_bound(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig aux_config; + int sentinel_hits = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t rule_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t dispatch_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_hitcount_t n_routes; + int i; + byte addr[4]; + +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* an aux event with no purge time would make every tracked peer a permanent route. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_unpurgeable", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &aux_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &aux_config.route_idle_time_for_purge)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_purgeable", + -1 /* label_len */, + 10, + &aux_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + WOLFSENTRY_BUILTIN_LABEL_PREFIX "track-peer-v1", + -1)); + + /* a refused insert must not stop the rest of the match chain. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_sentinel", + -1, + WOLFSENTRY_ACTION_FLAG_NONE, + test_track_peer_sentinel_action, + &sentinel_hits, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "track_peer_sentinel", + -1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + "track_peer_unpurgeable", + -1)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 0; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memset(remote.sa.addr, 0, sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + rule_flags, + "track_peer_generator", + -1, + &id, + &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_count_main_routes(WOLFSENTRY_CONTEXT_ARGS_OUT, &n_routes)); + WOLFSENTRY_EXIT_ON_FALSE(n_routes == 1); + + WOLFSENTRY_SET_BITS(dispatch_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + for (i = 1; i <= 3; ++i) { + remote.sa.sa_port = (wolfsentry_port_t)(1000 + i); + addr[0] = 10; + addr[1] = 0; + addr[2] = 0; + addr[3] = (byte)i; + memcpy(remote.sa.addr, addr, sizeof addr); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_count_main_routes(WOLFSENTRY_CONTEXT_ARGS_OUT, &n_routes)); + WOLFSENTRY_EXIT_ON_FALSE(n_routes == 1); + WOLFSENTRY_EXIT_ON_FALSE(sentinel_hits == 3); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + "track_peer_purgeable", + -1)); + + for (i = 1; i <= 3; ++i) { + remote.sa.sa_port = (wolfsentry_port_t)(2000 + i); + addr[0] = 10; + addr[1] = 0; + addr[2] = 1; + addr[3] = (byte)i; + memcpy(remote.sa.addr, addr, sizeof addr); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_count_main_routes(WOLFSENTRY_CONTEXT_ARGS_OUT, &n_routes)); + WOLFSENTRY_EXIT_ON_FALSE(n_routes == 4); + WOLFSENTRY_EXIT_ON_FALSE(sentinel_hits == 6); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +/* find the non-wildcard route for the peer whose remote address ends in remote_last_byte. */ +static wolfsentry_errcode_t test_dynamic_ephemeral_route_exports( + WOLFSENTRY_CONTEXT_ARGS_IN, + byte remote_last_byte, + struct wolfsentry_route_exports *exports_out) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; + struct wolfsentry_route_exports exports; + int found = 0; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + + ret = wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes); + if (ret >= 0) { + ret = wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + if (ret >= 0) { + for (ret = wolfsentry_route_table_iterate_current(main_routes, cursor, &route); + ret >= 0; + ret = wolfsentry_route_table_iterate_next(main_routes, cursor, &route)) + { + ret = wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &exports); + if (ret < 0) + break; + if ((! WOLFSENTRY_CHECK_BITS(exports.flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD)) && + (exports.remote.addr_len == 4 * BITS_PER_BYTE) && + (exports.remote_address[3] == remote_last_byte)) + { + *exports_out = exports; + found = 1; + break; + } + } + if (ret >= 0 || WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) + ret = wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + } + } + + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + if ((ret >= 0) && (! found)) + ret = WOLFSENTRY_ERROR_ENCODE(ITEM_NOT_FOUND); + + WOLFSENTRY_ERROR_RERETURN(ret); +} + +static int test_track_peer_connect_count(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig limit_config, nocount_config, nolimit_config; + struct wolfsentry_route_exports ephemeral_exports; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t rule_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t dispatch_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &limit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &limit_config.route_idle_time_for_purge)); + limit_config.max_connection_count = 1; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_limit", + -1 /* label_len */, + 10, + &limit_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &nocount_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &nocount_config.route_idle_time_for_purge)); + nocount_config.max_connection_count = 1; + nocount_config.route_flags_to_add_on_insert = WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_nocount", + -1 /* label_len */, + 10, + &nocount_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + WOLFSENTRY_BUILTIN_LABEL_PREFIX "track-peer-v1", + -1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + "track_peer_cc_limit", + -1)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 0; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memset(remote.sa.addr, 0, sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + rule_flags, + "track_peer_cc_generator", + -1, + &id, + &action_results)); + + WOLFSENTRY_SET_BITS(dispatch_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + remote.sa.sa_port = 1234; + memcpy(remote.sa.addr, "\12\0\2\1", sizeof remote.addr_buf); + + /* the CONNECT that spawns the tracking route must be counted on that route. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + /* with a per-peer limit of 1, a second concurrent CONNECT from the same peer is rejected. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + /* the matching DISCONNECT brings the count back to zero. */ + action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + /* and the peer can connect again. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + /* a tracking route flagged dont-count-current-connections stays at zero. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + "track_peer_cc_nocount", + -1)); + + remote.sa.sa_port = 2345; + memcpy(remote.sa.addr, "\12\0\3\2", sizeof remote.addr_buf); + + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 2, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(ephemeral_exports.flags, WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + /* an unlimited route still counts the spawning CONNECT, so a later DISCONNECT balances it. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &nolimit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &nolimit_config.route_idle_time_for_purge)); + nolimit_config.max_connection_count = 0; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_nolimit", + -1 /* label_len */, + 10, + &nolimit_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + "track_peer_cc_nolimit", + -1)); + + remote.sa.sa_port = 3456; + memcpy(remote.sa.addr, "\12\0\4\3", sizeof remote.addr_buf); + + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 3, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 3, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_max_length_label(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_event *event; + struct wolfsentry_eventconfig eventconfig; + char label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(label, 'm', WOLFSENTRY_MAX_LABEL_BYTES); + label[WOLFSENTRY_MAX_LABEL_BYTES] = 0; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &event)); + + WOLFSENTRY_EXIT_ON_FALSE(id == wolfsentry_get_object_id(event)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_MAX_LABEL_BYTES, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &event)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_max_length_label_lookups(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_action_list_ent *cursor; + const char *action_label; + int action_label_len; + struct wolfsentry_eventconfig eventconfig; + int n_actions = 0; + char label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(label, 'n', WOLFSENTRY_MAX_LABEL_BYTES); + label[WOLFSENTRY_MAX_LABEL_BYTES] = 0; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "max-label-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE(eventconfig.route_private_data_size == config.route_private_data_size); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "max-label-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_list_start( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + &cursor)); + + while (wolfsentry_event_action_list_next( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &cursor, + &action_label, + &action_label_len) >= 0) { + WOLFSENTRY_EXIT_ON_FALSE(strcmp(action_label, "max-label-action") == 0); + ++n_actions; + } + + WOLFSENTRY_EXIT_ON_FALSE(n_actions == 1); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_list_done( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &cursor)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "max-label-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_insert_failure_leaves_id_untouched(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id, id_before; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + id_before = id; + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_ALREADY_PRESENT, + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FALSE(id == id_before); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_update_config_null(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_eventconfig global_eventconfig, eventconfig; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &global_eventconfig)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* config */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == global_eventconfig.route_private_data_size) && + (eventconfig.route_private_data_alignment == global_eventconfig.route_private_data_alignment) && + (eventconfig.max_connection_count == global_eventconfig.max_connection_count)); + WOLFSENTRY_EXIT_ON_FALSE(eventconfig.max_connection_count == config.max_connection_count); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_update_config_layout_with_routes(void) { + struct wolfsentry_context *wolfsentry, *ctx_clone; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_eventconfig eventconfig; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; + static const struct wolfsentry_eventconfig config2_more_connections = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 20 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2_more_connections = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_no_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + memcpy(remote.sa.addr, "\4\5\6\7", sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config2.route_private_data_size) && + (eventconfig.route_private_data_alignment == config2.route_private_data_alignment) && + (eventconfig.max_connection_count == config2.max_connection_count)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2_more_connections)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE(eventconfig.max_connection_count == config2_more_connections.max_connection_count); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + "layout-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + memcpy(remote.sa.addr, "\10\11\12\13", sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_no_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_no_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config.route_private_data_size) && + (eventconfig.route_private_data_alignment == config.route_private_data_alignment)); + + /* a clone without routes has no route using the old layout */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_aux_event_self_reference(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_event *event; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &event)); + + WOLFSENTRY_EXIT_ON_TRUE(wolfsentry_event_get_aux_event(event) != NULL); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(wolfsentry_event_get_flags(event), WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_aux_event_reuse_after_clear(void) { + struct wolfsentry_context *wolfsentry, *ctx_clone; + struct wolfsentry_event *deleted_owner; + struct wolfsentry_event *held_aux; + wolfsentry_ent_id_t id; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* a -> b, then clear: b is free to own an aux event of its own */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* c is now an aux event, so it cannot own one */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* replacing a's aux event releases the old one */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "other_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* owner_one is deleted from the table but stays alive, and still owns shared_aux */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &deleted_owner)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "other_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + deleted_owner, + NULL /* action_results */)); + + /* freeing the deleted owner released shared_aux, which can now own an aux event */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "other_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_target", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* an ordinary reference on the aux event is not ownership */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &held_aux)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "held_target", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "held_target", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + held_aux, + NULL /* action_results */)); + + /* a clone rebuilds the aux ownership graph, independently of the source */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_aux_mark_cleared_on_owner_free(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_event *aux; + wolfsentry_ent_id_t id; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_owner", WOLFSENTRY_LENGTH_NULL_TERMINATED, 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_aux", WOLFSENTRY_LENGTH_NULL_TERMINATED, 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_set_aux_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_owner", WOLFSENTRY_LENGTH_NULL_TERMINATED, "mark_aux", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_aux", WOLFSENTRY_LENGTH_NULL_TERMINATED, &aux)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(wolfsentry_event_get_flags(aux), WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)); + + /* freeing the only owner leaves the aux event with no owners */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_owner", WOLFSENTRY_LENGTH_NULL_TERMINATED, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(wolfsentry_event_get_flags(aux), WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_event_aux_event_rejects_subevent_owner(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "subevent_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "subevent_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_aux_event_rejects_parent_event(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "route_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "route_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "route_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "route_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "route_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_former_aux_becomes_route_parent(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + static const char * const labels[3] = { "former_aux", "first_owner", "later_owner" }; + unsigned int n; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + for (n = 0; n < 3; ++n) { + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + labels[n], + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "first_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "former_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* freeing the owner drops the aux owner count without clearing the stored subevent mark */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "first_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "former_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "later_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "former_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#ifndef WOLFSENTRY_NO_JSON + +static int event_priority_check_winner(const char *priority_clause, int expect_accept) { + struct wolfsentry_context *wolfsentry; + wolfsentry_errcode_t ret; + wolfsentry_ent_id_t route_id; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + char err_buf[512]; + char json[2048]; + int json_len; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1," + " \"events-insert\" : [" + " { \"label\" : \"first-event\" %s }," + " { \"label\" : \"second-event\", \"priority\" : 1 } ]," + " \"static-routes-insert\" : [" + " { \"parent-event\" : \"first-event\", \"direction-in\" : true," + " \"green-listed\" : true, \"penalty-boxed\" : false, \"family\" : %u," + " \"remote\" : { \"address\" : \"10.0.0.0\", \"prefix-bits\" : 8 }," + " \"local\" : { \"address\" : \"10.0.0.0\", \"prefix-bits\" : 8 } }," + " { \"parent-event\" : \"second-event\", \"direction-in\" : true," + " \"green-listed\" : false, \"penalty-boxed\" : true, \"family\" : %u," + " \"remote\" : { \"address\" : \"10.1.2.3\" }," + " \"local\" : { \"address\" : \"10.0.0.0\", \"prefix-bits\" : 8 } } ] }", + priority_clause, (unsigned int)AF_INET, (unsigned int)AF_INET); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + + ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + err_buf, + sizeof err_buf); + if (ret < 0) + (void)fprintf(stderr, "%s\n", err_buf); + WOLFSENTRY_EXIT_ON_FAILURE(ret); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\12\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\12\11\11\11", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + + if (expect_accept) { + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } else { + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_default_priority(void) { + /* an omitted priority behaves as priority 0, the highest */ + WOLFSENTRY_EXIT_ON_FAILURE(event_priority_check_winner("", 1)); + WOLFSENTRY_EXIT_ON_FAILURE(event_priority_check_winner(", \"priority\" : 0", 1)); + WOLFSENTRY_EXIT_ON_FAILURE(event_priority_check_winner(", \"priority\" : 2", 0)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_json_event_max_length_label(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_errcode_t ret; + struct wolfsentry_eventconfig config; + char label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + char err_buf[512]; + char json[WOLFSENTRY_MAX_LABEL_BYTES + 128]; + int json_len; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(label, 'L', sizeof label); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1, \"events-insert\" : [ { \"label\" : \"%.*s\" } ] }", + (int)WOLFSENTRY_MAX_LABEL_BYTES, label); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + err_buf, + sizeof err_buf); + if (ret < 0) + (void)fprintf(stderr, "%s\n", err_buf); + WOLFSENTRY_EXIT_ON_FAILURE(ret); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + (int)WOLFSENTRY_MAX_LABEL_BYTES, + &config)); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1, \"events-insert\" : [ { \"label\" : \"%.*s\" } ] }", + (int)WOLFSENTRY_MAX_LABEL_BYTES + 1, label); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + err_buf, + sizeof err_buf)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* !WOLFSENTRY_NO_JSON */ + +static wolfsentry_errcode_t test_action_insert_noop( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_arg; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + WOLFSENTRY_RETURN_OK; +} + +static int test_action_insert_failure_leaves_id_untouched(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id, id_before; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action_insert_noop, + NULL /* handler_arg */, + &id)); + + id_before = id; + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_ALREADY_PRESENT, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action_insert_noop, + NULL /* handler_arg */, + &id)); + + WOLFSENTRY_EXIT_ON_FALSE(id == id_before); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_aux_event_layout_pinned(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* aux_event_label */, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_layout_guard_held_deleted_route(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *route = NULL; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action_insert_noop, + NULL /* handler_arg */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + "layout_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_and_check_out( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &route, + &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + route->header.id, + NULL /* trigger_label */, + 0, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* TEST_DYNAMIC_RULES */ + +#ifdef TEST_USER_VALUES + +#include + +static wolfsentry_errcode_t test_kv_validator( + WOLFSENTRY_CONTEXT_ARGS_IN, + struct wolfsentry_kv_pair *kv) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + switch (WOLFSENTRY_KV_TYPE(kv)) { + case WOLFSENTRY_KV_NONE: + case WOLFSENTRY_KV_NULL: + case WOLFSENTRY_KV_TRUE: + case WOLFSENTRY_KV_FALSE: + WOLFSENTRY_RETURN_OK; + case WOLFSENTRY_KV_UINT: + if (WOLFSENTRY_KV_V_UINT(kv) == 12345678UL) + WOLFSENTRY_ERROR_RETURN(BAD_VALUE); + else + WOLFSENTRY_RETURN_OK; + case WOLFSENTRY_KV_SINT: + if (WOLFSENTRY_KV_V_SINT(kv) == -12345678L) + WOLFSENTRY_ERROR_RETURN(BAD_VALUE); + else + WOLFSENTRY_RETURN_OK; + case WOLFSENTRY_KV_FLOAT: + if (WOLFSENTRY_KV_V_FLOAT(kv) > 100.0) + WOLFSENTRY_ERROR_RETURN(BAD_VALUE); + else + WOLFSENTRY_RETURN_OK; + case WOLFSENTRY_KV_STRING: + if (WOLFSENTRY_KV_V_STRING_LEN(kv) != 8) + WOLFSENTRY_RETURN_OK; + if (strncmp(WOLFSENTRY_KV_V_STRING(kv), "deadbeef", WOLFSENTRY_KV_V_STRING_LEN(kv)) == 0) + WOLFSENTRY_ERROR_RETURN(BAD_VALUE); + else + WOLFSENTRY_RETURN_OK; + case WOLFSENTRY_KV_BYTES: + if (WOLFSENTRY_KV_V_BYTES_LEN(kv) != 10) + WOLFSENTRY_RETURN_OK; + if (memcmp(WOLFSENTRY_KV_V_STRING(kv), "abcdefghij", WOLFSENTRY_KV_V_BYTES_LEN(kv)) == 0) + WOLFSENTRY_ERROR_RETURN(BAD_VALUE); + else + WOLFSENTRY_RETURN_OK; + default: + break; + } + WOLFSENTRY_ERROR_RETURN(WRONG_TYPE); +} + +static int test_user_values(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + + wolfsentry_kv_type_t kv_type; + struct wolfsentry_kv_pair_internal *kv_ref; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + action_results = WOLFSENTRY_ACTION_RES_NONE; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_set_validator(WOLFSENTRY_CONTEXT_ARGS_OUT, test_kv_validator, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(action_results == WOLFSENTRY_ACTION_RES_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_null( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_null", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_bool( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_KV_TRUE, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_bool( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &kv_type)); + + WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_TRUE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_bool( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_KV_FALSE, + 1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_bool( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &kv_type)); + + WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_FALSE); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + WRONG_TYPE, + wolfsentry_user_value_store_bool( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_KV_NONE, + 1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_type( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &kv_type)); + + WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_FALSE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_type( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + strlen("test_bool"), + &kv_type)); + + WOLFSENTRY_EXIT_ON_FALSE(kv_type == WOLFSENTRY_KV_FALSE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_user_value_get_type( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bool", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &kv_type)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_uint", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 123UL, + 0)); + + { + uint64_t value = 0; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_uint", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value)); + + WOLFSENTRY_EXIT_ON_FALSE(value == 123UL); + } + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + BAD_VALUE, + wolfsentry_user_value_store_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "bad_uint", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 12345678UL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_sint", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + -123L, + 0)); + + { + int64_t value = 0; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_sint", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value)); + + WOLFSENTRY_EXIT_ON_FALSE(value == -123L); + } + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + BAD_VALUE, + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "bad_sint", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + -12345678L, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_float", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 1.234, + 0)); + + { + double value = 0.0; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_float( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_float", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value)); + + WOLFSENTRY_EXIT_ON_FALSE(fabs(value - 1.234) < 0.000001); + } + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + BAD_VALUE, + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "bad_float", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 123.45678, + 0)); + + { + static const char test_string[] = "abc123"; + const char *value = NULL; + int value_len = -1; + int mutable = -1; + wolfsentry_errcode_t ret; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + test_string, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &mutable)); + + WOLFSENTRY_EXIT_ON_FALSE(mutable == 1); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_set_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &mutable)); + + WOLFSENTRY_EXIT_ON_FALSE(mutable == 0); + + WOLFSENTRY_EXIT_ON_SUCCESS( + ret = wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NOT_PERMITTED, ret); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, + &value_len, + &kv_ref)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(test_string)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, test_string) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &kv_ref)); + + } + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* strings rendered as JSON must escape quotes and backslashes. */ + { + static const char raw[] = "a\"b\\c"; + const struct wolfsentry_kv_pair *kv_exports; + char render_buf[64]; + int render_buf_space = (int)sizeof render_buf; + + memset(render_buf, 0, sizeof render_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "json_escape_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + raw, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + { + const char *value = NULL; + int value_len = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "json_escape_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, render_buf, &render_buf_space)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(strstr(render_buf, "\\\"") != NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(render_buf, "\\\\") != NULL); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_release_record(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_ref)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "json_escape_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + BAD_VALUE, + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "bad_string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "deadbeef", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + { + static const byte test_bytes[] = { 0, 1, 2, 3, 4 }; + const byte *value = NULL; + int value_len = -1; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_bytes( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bytes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + test_bytes, + sizeof test_bytes, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_bytes( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test_bytes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, + &value_len, + &kv_ref)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)sizeof test_bytes); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(value, test_bytes, (size_t)value_len) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &kv_ref)); + } + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + BAD_VALUE, + wolfsentry_user_value_store_bytes( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "bad_bytes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (const byte *)"abcdefghij", + 10, + 0)); + +#ifndef WOLFSENTRY_NO_STDIO + { + wolfsentry_errcode_t ret; + struct wolfsentry_cursor *cursor; + const struct wolfsentry_kv_pair *kv_exports; + const char *val_type; + char val_buf[256]; + int val_buf_space; + wolfsentry_hitcount_t n_seen = 0; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + for (ret = wolfsentry_user_values_iterate_current(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref); + ret >= 0; + ret = wolfsentry_user_values_iterate_next(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref)) { + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); + val_buf_space = sizeof val_buf; + if (wolfsentry_kv_type_to_string(WOLFSENTRY_KV_TYPE(kv_exports), &val_type) < 0) + val_type = "?"; + if (wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, val_buf, &val_buf_space) < 0) + strcpy(val_buf,"?"); + printf("{ \"%.*s\" : { \"%s\" : %s } }\n", + (int)WOLFSENTRY_KV_KEY_LEN(kv_exports), + WOLFSENTRY_KV_KEY(kv_exports), + val_type, + val_buf); + ++n_seen; + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->user_values->header.n_ents); + WOLFSENTRY_EXIT_ON_FALSE(n_seen == 6); + } +#endif + + { + static const struct { + const char *q; + const char *a; + } base64_qna[] = { + { "", "" }, + { "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu", "Many hands make light work." }, + { "bGlnaHQgd29yay4=", "light work." }, + { "bGlnaHQgd29yay4", "light work." }, + { "bGlnaHQgd29yaw==", "light work" }, + { "bGlnaHQgd29yaw", "light work" }, + { "bGlnaHQgd29y", "light wor" }, + { "bGlnaHQgd28=", "light wo" }, + { "bGlnaHQgdw==", "light w" }, + { "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV" + "1hZWjAxMjM0NTY3ODkhQCMkJV4mKigpXy0rPXxcYH5bXXt9OzonIiw8Lj4vPw==", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "!@#$%^&*()_-+=|\\`~[]{};:'\",<.>/?" }, + { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/+", + "\x69\xb7\x1d\x79\xf8\x21\x8a\x39\x25\x9a\x7a\x29\xaa\xbb\x2d\xba" + "\xfc\x31\xcb\x30\x01\x08\x31\x05\x18\x72\x09\x28\xb3\x0d\x38\xf4" + "\x11\x49\x35\x15\x59\x76\x19\xd3\x5d\xb7\xe3\x9e\xbb\xf3\xdf\xfe" + } + }; + + int i; + byte outbuf[256]; + size_t outbuf_spc; + + for (i=0; i < (int)(sizeof base64_qna / sizeof base64_qna[0]); ++i) { + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_base64_decode(base64_qna[i].q, strlen(base64_qna[i].q), outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + WOLFSENTRY_EXIT_ON_FALSE((outbuf_spc == strlen(base64_qna[i].a)) && (memcmp(outbuf, base64_qna[i].a, outbuf_spc) == 0)); + } + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_reference_lifetime(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_kv_pair_internal *kv_ref = NULL; + const char *value = NULL; + int value_len = -1; + static const char key[] = "lifetime_key"; + static const char str[] = "lifetime_value"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + str, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + + /* delete the key while the reference returned by get_string is still held; + * the held reference must keep the entry alive and readable. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(str)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, str) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_ref)); + + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_get2_reference_balance(void) { + struct wolfsentry_context *wolfsentry; + static const char key[] = "balance_key"; + uint64_t uint_value = 0; + int mutable = -1; + int i; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 42, 0)); + + /* the non-reference getters take and release an internal reference each + * call; repeated calls must leave the refcount balanced so the entry can + * still be deleted afterwards. + */ + for (i = 0; i < 100; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &uint_value)); + WOLFSENTRY_EXIT_ON_FALSE(uint_value == 42); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &mutable)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_user_value_get_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &uint_value)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_get_bad_key_errcode(void) { + struct wolfsentry_context *wolfsentry; + const char *value = NULL; + int value_len = -1; + struct wolfsentry_kv_pair_internal *kv_ref = NULL; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* a rejected key must surface the validation error itself, not an error + * from an unlock attempted without a held lock. + */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "unused_key", 0 /* key_len */, + &value, &value_len, &kv_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int render_only_user_value( + WOLFSENTRY_CONTEXT_ARGS_IN, + char *out, + int out_space) +{ + struct wolfsentry_cursor *cursor; + struct wolfsentry_kv_pair_internal *kv_ref; + const struct wolfsentry_kv_pair *kv_exports; + int render_space = out_space; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_current(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, out, &render_space)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_render_large_int(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + /* 0x1234567890ABCDEF and -(2^33), built without long-long literals so the + * test stays legal under WOLFSENTRY_C89; compared against the exact decimal + * rendering to avoid strtoull()/strtoll(). + */ + uint64_t stored_uint = ((uint64_t)0x12345678u << 32) | (uint64_t)0x90abcdefu; + int64_t stored_sint = -((int64_t)1 << 33); + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "big_uint", WOLFSENTRY_LENGTH_NULL_TERMINATED, + stored_uint, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(buf, "1311768467294899695") == 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, "big_uint", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "big_sint", WOLFSENTRY_LENGTH_NULL_TERMINATED, + stored_sint, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(buf, "-8589934592") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_render_float_roundtrip(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + double stored = 3.141592653589793; +#ifndef WOLFSENTRY_NO_JSON + char json[128]; + int json_len; + double reloaded; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "pi", WOLFSENTRY_LENGTH_NULL_TERMINATED, + stored, 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + + /* the rendered form must reparse to the exact same double. */ + WOLFSENTRY_EXIT_ON_FALSE(strtod(buf, NULL) == stored); + +#ifndef WOLFSENTRY_NO_JSON + /* the longest %.17g form, 24 characters, must reload through the JSON loader */ + stored = -2.2250738585072014e-308; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "pi", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_store_double(WOLFSENTRY_CONTEXT_ARGS_OUT, "tiny", WOLFSENTRY_LENGTH_NULL_TERMINATED, stored, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(strlen(buf) == 24); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "tiny", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + json_len = snprintf(json, sizeof json, "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"tiny\" : %s } }", buf); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_config_json_oneshot(WOLFSENTRY_CONTEXT_ARGS_OUT, (const unsigned char *)json, (size_t)json_len, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_get_float(WOLFSENTRY_CONTEXT_ARGS_OUT, "tiny", WOLFSENTRY_LENGTH_NULL_TERMINATED, &reloaded)); + WOLFSENTRY_EXIT_ON_FALSE(reloaded == stored); +#endif + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#ifndef WOLFSENTRY_C89 +static int test_kv_render_nonfinite_float(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + double reparsed; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "nan_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, + strtod("nan", NULL), 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + /* a bare nan/inf is not valid JSON; the rendered value must be quoted and + * must reparse to the same non-finite value. + */ + WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); + reparsed = strtod(buf + 1, NULL); + WOLFSENTRY_EXIT_ON_FALSE(isnan(reparsed)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, "nan_val", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "inf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, + strtod("inf", NULL), 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); + reparsed = strtod(buf + 1, NULL); + WOLFSENTRY_EXIT_ON_FALSE(isinf(reparsed) && (reparsed > 0.0)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, "inf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ninf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, + -strtod("inf", NULL), 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); + reparsed = strtod(buf + 1, NULL); + WOLFSENTRY_EXIT_ON_FALSE(isinf(reparsed) && (reparsed < 0.0)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} +#endif /* !WOLFSENTRY_C89 */ + +static wolfsentry_errcode_t failing_mk_id_cb(void *context, wolfsentry_ent_id_t *id) { + (void)context; + (void)id; + WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); +} + +static int test_kv_replace_keeps_value_on_id_failure(void) { + struct wolfsentry_context *wolfsentry; + int64_t value = 0; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, "replaced", WOLFSENTRY_LENGTH_NULL_TERMINATED, 1, 1 /* overwrite_p */)); + + wolfsentry->mk_id_cb = failing_mk_id_cb; + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, "replaced", WOLFSENTRY_LENGTH_NULL_TERMINATED, 2, 1 /* overwrite_p */)); + wolfsentry->mk_id_cb = NULL; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, "replaced", WOLFSENTRY_LENGTH_NULL_TERMINATED, &value)); + WOLFSENTRY_EXIT_ON_FALSE(value == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_empty_key_rejected(void) { + struct wolfsentry_context *wolfsentry; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* an empty key must be rejected consistently, whether supplied as an + * explicit zero length or as a null-terminated empty string. + */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "", 0 /* key_len */, + "v", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "", WOLFSENTRY_LENGTH_NULL_TERMINATED, + "v", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_readonly_overwrite_rejected(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_kv_pair_internal *kv_ref = NULL; + const char *value = NULL; + int value_len = -1; + static const char key[] = "ro_key"; + static const char orig[] = "orig"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + orig, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_set_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* not mutable */)); + + /* overwriting a read-only value must be rejected. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + "different", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 1 /* overwrite_p */)); + + /* and the value must be unchanged. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(orig)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, orig) == 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#ifndef WOLFSENTRY_HAVE_JSON_DOM +static int test_kv_render_string_escape(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + static const char raw[] = "a\"b\\c\nd"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "esc", WOLFSENTRY_LENGTH_NULL_TERMINATED, + raw, (int)(sizeof raw - 1), + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + + /* the rendered JSON string must not contain a raw control byte, and the + * quote, backslash and newline must all appear escaped. + */ + WOLFSENTRY_EXIT_ON_FALSE(strchr(buf + 1, '\n') == NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(buf, "\\\"") != NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(buf, "\\\\") != NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(buf, "\\n") != NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} +#endif /* !WOLFSENTRY_HAVE_JSON_DOM */ + +#endif /* TEST_USER_VALUES */ + +#ifdef TEST_USER_ADDR_FAMILIES + +static int test_user_addr_families(void) { + + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; +#ifdef WOLFSENTRY_PROTOCOL_NAMES + char overlong_family_name[WOLFSENTRY_MAX_LABEL_BYTES + 2]; + wolfsentry_addr_family_t unspec_family_number; + const char *unspec_family_name; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + "my_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_remove_bynumber( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + "my_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); + + action_results = 0; + + /* exercise the plugins to disambiguate failures in the plugins from + * JSON-specific failures. + */ + { + byte addr_internal[3]; + wolfsentry_addr_bits_t addr_internal_len; + char addr_text[13]; + int addr_text_len; + + addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_parser( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "377/377/377", + strlen("377/377/377"), + addr_internal, + &addr_internal_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == (wolfsentry_addr_bits_t)(sizeof addr_internal * 8)); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\377\377\377", sizeof addr_internal) == 0); + + addr_text_len = (int)sizeof addr_text; + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_formatter( + WOLFSENTRY_CONTEXT_ARGS_OUT, + addr_internal, + (int)(sizeof addr_internal * 8), + addr_text, + &addr_text_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_text_len == strlen("377/377/377")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(addr_text, "377/377/377") == 0); + + addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_parser( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "0/0/0", + strlen("0/0/0"), + addr_internal, + &addr_internal_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == (wolfsentry_addr_bits_t)(sizeof addr_internal * 8)); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\0\0\0", sizeof addr_internal) == 0); + + addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_parser( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "377/377/", + strlen("377/377/"), + addr_internal, + &addr_internal_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == 16); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\377\377", 2) == 0); + + addr_text_len = (int)sizeof addr_text; + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_formatter( + WOLFSENTRY_CONTEXT_ARGS_OUT, + addr_internal, + 16, + addr_text, + &addr_text_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_text_len == strlen("377/377/")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(addr_text, "377/377/") == 0); + + addr_internal_len = (wolfsentry_addr_bits_t)(sizeof addr_internal * 8); + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_parser( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "377/", + strlen("377/"), + addr_internal, + &addr_internal_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_internal_len == 8); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(addr_internal, "\377", 1) == 0); + + addr_text_len = (int)sizeof addr_text; + WOLFSENTRY_EXIT_ON_FAILURE( + my_addr_family_formatter( + WOLFSENTRY_CONTEXT_ARGS_OUT, + addr_internal, + 8, + addr_text, + &addr_text_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_text_len == strlen("377/")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(addr_text, "377/") == 0); + } + +#ifdef WOLFSENTRY_PROTOCOL_NAMES + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_remove_byname( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "my_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + "my_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); + + { + wolfsentry_addr_family_t family_number; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "my_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &family_number)); + WOLFSENTRY_EXIT_ON_FALSE(family_number == WOLFSENTRY_AF_USER_OFFSET); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "no_such_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, &family_number)); + } + + { + struct wolfsentry_addr_family_bynumber *addr_family = NULL; + const char *family_name; + + WOLFSENTRY_EXIT_ON_FAILURE( + (wolfsentry_addr_family_ntop( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + &addr_family, + &family_name))); + WOLFSENTRY_EXIT_ON_FALSE((family_name != NULL) && + (! strcmp(family_name,"my_AF"))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, addr_family, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + } + +#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + + { + wolfsentry_addr_family_parser_t parser; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_get_parser( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + &parser)); + WOLFSENTRY_EXIT_ON_FALSE(parser == my_addr_family_parser); + } + { + wolfsentry_addr_family_formatter_t formatter; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_get_formatter( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + &formatter)); + WOLFSENTRY_EXIT_ON_FALSE(formatter == my_addr_family_formatter); + } + +#ifdef WOLFSENTRY_PROTOCOL_NAMES + { + wolfsentry_addr_family_t family_number; + wolfsentry_addr_bits_t bits; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "inet", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &family_number)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT, + family_number, + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 32); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "inet6", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &family_number)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT, + family_number, + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 128); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "link", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &family_number)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT, + family_number, + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 48); + + /* LINK64 must roundtrip through pton → ntop → verify label. */ + { + struct wolfsentry_addr_family_bynumber *addr_family = NULL; + const char *family_name = NULL; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "LINK64", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &family_number)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_ntop( + WOLFSENTRY_CONTEXT_ARGS_OUT, + family_number, + &addr_family, + &family_name)); + WOLFSENTRY_EXIT_ON_FALSE((family_name != NULL) && (! strcmp(family_name, "LINK64"))); + if (addr_family) + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_addr_family_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, addr_family, &action_results)); + } + } +#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + +#ifdef WOLFSENTRY_PROTOCOL_NAMES + /* A family name longer than the maximum label length must propagate + * STRING_ARG_TOO_LONG rather than a lock-management error from an error + * path that returns before the context lock is acquired. + */ + memset(overlong_family_name, 'a', sizeof overlong_family_name - 1); + overlong_family_name[sizeof overlong_family_name - 1] = 0; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET + 1, + overlong_family_name, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); +#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + +#ifdef WOLFSENTRY_PROTOCOL_NAMES + /* WOLFSENTRY_AF_UNSPEC formats to "UNSPEC" through ntop and must parse back + * to the same number through pton so the encode/decode roundtrip holds. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_ntop( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_UNSPEC, + NULL, + &unspec_family_name)); + WOLFSENTRY_EXIT_ON_FALSE((unspec_family_name != NULL) && + (! strcmp(unspec_family_name, "UNSPEC"))); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "UNSPEC", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &unspec_family_number)); + WOLFSENTRY_EXIT_ON_FALSE(unspec_family_number == WOLFSENTRY_AF_UNSPEC); +#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* TEST_USER_ADDR_FAMILIES */ + +#ifdef TEST_JSON + +#include "wolfsentry/wolfsentry_json.h" +#ifdef WOLFSENTRY_HAVE_JSON_DOM +#include + +static int test_dom_allocator_marker; +static int test_dom_allocator_context_ok; +static int test_dom_allocator_malloc_calls; + +static void *test_dom_allocator_malloc( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(void *context), size_t size) +{ + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + ++test_dom_allocator_malloc_calls; + if (context != (void *)&test_dom_allocator_marker) + test_dom_allocator_context_ok = 0; + return malloc(size); +} + +static void test_dom_allocator_free( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(void *context), void *ptr) +{ + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + if (context != (void *)&test_dom_allocator_marker) + test_dom_allocator_context_ok = 0; + free(ptr); +} + +static void *test_dom_allocator_realloc( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(void *context), void *ptr, size_t size) +{ + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + if (context != (void *)&test_dom_allocator_marker) + test_dom_allocator_context_ok = 0; + return realloc(ptr, size); +} +#endif + +static int test_json(const char *fname, const char *extra_fname) { + wolfsentry_errcode_t ret; + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET, + "my_AF", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET + 1, + "my_AF2", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); + + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NO_ROUTES_OR_EVENTS, 1)); + + { + static const char test_string[] = "hello"; + const char *value = NULL; + int value_len = -1; + struct wolfsentry_kv_pair_internal *kv_ref; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "user-string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, + &value_len, + &kv_ref)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(test_string)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, test_string) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &kv_ref)); + } + + { + static const char user_cert_string[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIDnzCCAyWgAwIBAgICEAEwCgYIKoZIzj0EAwMwgZcxCzAJBgNVBAYTAlVTMRMw\n" + "EQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMRAwDgYDVQQKDAd3\n" + "b2xmU1NMMRQwEgYDVQQLDAtEZXZlbG9wbWVudDEYMBYGA1UEAwwPd3d3LndvbGZz\n" + "c2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tMCAXDTIyMDIx\n" + "NTEyNTAyNFoYDzIwNTIwMjA4MTI1MDI0WjCBlTELMAkGA1UEBhMCVVMxEzARBgNV\n" + "BAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxEDAOBgNVBAoMB0VsaXB0\n" + "aWMxEjAQBgNVBAsMCUVDQzM4NFNydjEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29t\n" + "MR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tMHYwEAYHKoZIzj0CAQYF\n" + "K4EEACIDYgAE6s+TTywJuzkUD1Zkw0C03w5jruVxSwDMBJf/4ek4lrtfkbJqzLU5\n" + "X49wWfEB9lorAWxoC89VJa9tmEgKqHTJqRegDMP70yNo/gQ8Y1CIO7lPfGc09zup\n" + "c+cbw1FeIhjso4IBQDCCATwwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAw\n" + "HQYDVR0OBBYEFII78mUv87QAxrwG/XlCdUtl0c68MIHXBgNVHSMEgc8wgcyAFKvg\n" + "wyZMGNRyu9KEjJwKBZKAElNSoYGdpIGaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UE\n" + "CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEQMA4GA1UECgwHd29sZlNT\n" + "TDEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNv\n" + "bTEfMB0GCSqGSIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbYIUaiYbTQIaM//CRxT5\n" + "51VgWi5/ESkwDgYDVR0PAQH/BAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAoG\n" + "CCqGSM49BAMDA2gAMGUCMQCEPZBU/y/EetTYGOKzLbtCN0CmHwmD3rwEeoLcVRdC\n" + "XBeqB0LcyPZQzRS3Bhk5HyQCMBNiS5/JoIzSac8WToa9nik4ROlKOmOgZjiV4n3j\n" + "F+yUIbg9aV7K5ISc2mF9G1G/0Q==\n" + "-----END CERTIFICATE-----\n"; + const char *value = NULL; + int value_len = -1; + struct wolfsentry_kv_pair_internal *kv_ref; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "user-cert-string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, + &value_len, + &kv_ref)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(user_cert_string)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, user_cert_string) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &kv_ref)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(load_test_action_handlers(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_DRY_RUN, 1)); + + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, 1)); + + WOLFSENTRY_EXIT_ON_SUCCESS(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, 0)); + + { + static const char *bad_json[] = { + "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"user-string\" : \"should collide\" } }", + "{ \"wolfsentry-config-version\" : 2, \"user-values\" : { \"ok-user-string\" : \"shouldn't collide\" } }", + "{ \"wolfsentry-config-version\" : 1, \"not-user-values\" : { \"ok-user-string\" : \"shouldn't collide\" } }", + "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"too-long-user-string-123456789-abcdefghi\" : \"x\" } }", + }; + const char **bad_json_i; + for (bad_json_i = bad_json; bad_json_i < &bad_json[length_of_array(bad_json)]; ++bad_json_i) { + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)*bad_json_i, + strlen(*bad_json_i), + WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, + NULL, + 0)); + } + } + + { + struct wolfsentry_route_table *main_routes, main_routes_copy; + const char *value = NULL; + int value_len = -1; + struct wolfsentry_kv_pair_internal *kv_ref; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_ent_id_t route_id; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + main_routes_copy = *main_routes; + + if (extra_fname) { + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 13579; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + + memcpy(remote.sa.addr,"\12\24\36\50",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\62\74\106\120",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == WOLFSENTRY_ENT_ID_NONE); + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_FALLTHROUGH)); + + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, extra_fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, 1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == WOLFSENTRY_ENT_ID_NONE); + WOLFSENTRY_EXIT_ON_FALSE(action_results == WOLFSENTRY_ACTION_RES_ACCEPT); + + memcpy(remote.sa.addr,"\13\24\36\50",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == WOLFSENTRY_ENT_ID_NONE); + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_FALLTHROUGH)); + } else { + static const char *trivial_test_json = "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"extra-user-string\" : \"extra hello\" } }"; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)trivial_test_json, + strlen(trivial_test_json), + WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT, + NULL, + 0)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + + WOLFSENTRY_EXIT_ON_FALSE((main_routes->max_purgeable_routes == main_routes_copy.max_purgeable_routes) && + (main_routes->default_policy == main_routes_copy.default_policy)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "user-string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, + &value_len, + &kv_ref)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen("hello")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, "hello") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &kv_ref)); + + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "extra-user-string", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, + &value_len, + &kv_ref)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen("extra hello")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, "extra hello") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &kv_ref)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, 1)); + + { + struct wolfsentry_route_table *main_routes, main_routes_copy; + struct wolfsentry_cursor *cursor; + WOLFSENTRY_BYTE_STREAM_DECLARE_STACK(json_out, 16384); + WOLFSENTRY_BYTE_STREAM_DECLARE_HEAP(json_out2, 16384); + wolfsentry_hitcount_t n_seen = 0; + char err_buf[512]; + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_BYTE_STREAM_INIT_HEAP(json_out2) != NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + + WOLFSENTRY_BYTE_STREAM_RESET(json_out); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_dump_json_start( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out), + WOLFSENTRY_BYTE_STREAM_SPC(json_out), + WOLFSENTRY_FORMAT_FLAG_NONE)); + + for (;;) { + ret = wolfsentry_route_table_dump_json_next( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out), + WOLFSENTRY_BYTE_STREAM_SPC(json_out), + WOLFSENTRY_FORMAT_FLAG_NONE); + if (ret < 0) { + if (! WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) + WOLFSENTRY_EXIT_ON_FAILURE(ret); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_dump_json_end( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out), + WOLFSENTRY_BYTE_STREAM_SPC(json_out), + WOLFSENTRY_FORMAT_FLAG_NONE)); + } else + ++n_seen; + if (ret < 0) + break; + } + + WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->routes->header.n_ents); + + main_routes_copy = *main_routes; + + ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_BYTE_STREAM_HEAD(json_out), + WOLFSENTRY_BYTE_STREAM_LEN(json_out), + WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT | WOLFSENTRY_CONFIG_LOAD_FLAG_FLUSH_ONLY_ROUTES, + err_buf, + sizeof err_buf); + if (ret < 0) { + (void)fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); + WOLFSENTRY_EXIT_ON_FAILURE(ret); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + + WOLFSENTRY_EXIT_ON_FALSE((main_routes->max_purgeable_routes == main_routes_copy.max_purgeable_routes) && + (main_routes->default_policy == main_routes_copy.default_policy)); + + WOLFSENTRY_BYTE_STREAM_RESET(json_out2); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_dump_json_start( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out2), + WOLFSENTRY_BYTE_STREAM_SPC(json_out2), + WOLFSENTRY_FORMAT_FLAG_NONE)); + + for (;;) { + ret = wolfsentry_route_table_dump_json_next( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out2), + WOLFSENTRY_BYTE_STREAM_SPC(json_out2), + WOLFSENTRY_FORMAT_FLAG_NONE); + if (ret < 0) { + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_dump_json_end( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out2), + WOLFSENTRY_BYTE_STREAM_SPC(json_out2), + WOLFSENTRY_FORMAT_FLAG_NONE)); + break; + } + } + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_BYTE_STREAM_LEN(json_out2) == WOLFSENTRY_BYTE_STREAM_LEN(json_out)); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(json_out, json_out2, WOLFSENTRY_BYTE_STREAM_LEN(json_out)) == 0); + + ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_BYTE_STREAM_HEAD(json_out), + WOLFSENTRY_BYTE_STREAM_LEN(json_out), + WOLFSENTRY_CONFIG_LOAD_FLAG_FLUSH_ONLY_ROUTES, + err_buf, + sizeof err_buf); + if (ret < 0) { + (void)fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); + WOLFSENTRY_EXIT_ON_FAILURE(ret); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + + WOLFSENTRY_BYTE_STREAM_RESET(json_out2); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_dump_json_start( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &cursor, WOLFSENTRY_BYTE_STREAM_PTR(json_out2), WOLFSENTRY_BYTE_STREAM_SPC(json_out2), WOLFSENTRY_FORMAT_FLAG_NONE)); - for (;;) { - ret = wolfsentry_route_table_dump_json_next( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out2), - WOLFSENTRY_BYTE_STREAM_SPC(json_out2), - WOLFSENTRY_FORMAT_FLAG_NONE); - if (ret < 0) { - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)); + for (;;) { + ret = wolfsentry_route_table_dump_json_next( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out2), + WOLFSENTRY_BYTE_STREAM_SPC(json_out2), + WOLFSENTRY_FORMAT_FLAG_NONE); + if (ret < 0) { + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_dump_json_end( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &cursor, + WOLFSENTRY_BYTE_STREAM_PTR(json_out2), + WOLFSENTRY_BYTE_STREAM_SPC(json_out2), + WOLFSENTRY_FORMAT_FLAG_NONE)); + break; + } + } + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_BYTE_STREAM_LEN(json_out2) == WOLFSENTRY_BYTE_STREAM_LEN(json_out)); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(json_out, json_out2, WOLFSENTRY_BYTE_STREAM_LEN(json_out)) == 0); + + WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->routes->header.n_ents); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_BYTE_STREAM_FREE_HEAP(json_out2); + } + + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, 1)); + + { + struct wolfsentry_context *ctx_clone; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)); + WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, 1)); + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + { + static const char *sequential_test_json = "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"user-json2\" : { \"json\" : { \"z\" : 26, \"y\" : 25, \"x\" : 24 } } } }"; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + (const unsigned char *)sequential_test_json, + strlen(sequential_test_json), + WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_JSON_DOM_MAINTAINDICTORDER, + NULL, + 0)); + } +#endif + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, ctx_clone)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + } + + { + struct wolfsentry_context *ctx_clone; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + } + + { + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; + struct wolfsentry_route_exports route_exports; + struct wolfsentry_route_table *main_routes; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + for (ret = wolfsentry_route_table_iterate_current(main_routes, cursor, &route); + ret >= 0; + ret = wolfsentry_route_table_iterate_next(main_routes, cursor, &route)) { + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &route_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_exports_render(WOLFSENTRY_CONTEXT_ARGS_OUT, &route_exports, stdout)); + putc('\n', stdout); + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + } + +#ifndef WOLFSENTRY_NO_STDIO + { + struct wolfsentry_kv_pair_internal *kv_ref; + struct wolfsentry_cursor *cursor; + const struct wolfsentry_kv_pair *kv_exports; + const char *val_type; + char val_buf[2048]; + int val_buf_space; + wolfsentry_hitcount_t n_seen = 0; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + for (ret = wolfsentry_user_values_iterate_current(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref); + ret >= 0; + ret = wolfsentry_user_values_iterate_next(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref)) { + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); + val_buf_space = sizeof val_buf; + if (wolfsentry_kv_type_to_string(WOLFSENTRY_KV_TYPE(kv_exports), &val_type) < 0) + val_type = "?"; + if (wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, val_buf, &val_buf_space) < 0) { + if (WOLFSENTRY_KV_TYPE(kv_exports) == WOLFSENTRY_KV_BYTES) + (void)snprintf(val_buf, sizeof val_buf, "<%.*s>", (int)WOLFSENTRY_KV_V_BYTES_LEN(kv_exports), WOLFSENTRY_KV_V_BYTES(kv_exports)); + else + strcpy(val_buf,"?"); + } + switch (WOLFSENTRY_KV_TYPE(kv_exports)) { + case WOLFSENTRY_KV_TRUE: + case WOLFSENTRY_KV_FALSE: + case WOLFSENTRY_KV_NULL: + printf("{ \"%.*s\" : %s }\n", + (int)WOLFSENTRY_KV_KEY_LEN(kv_exports), + WOLFSENTRY_KV_KEY(kv_exports), + val_buf); + break; + default: + printf("{ \"%.*s\" : { \"%s\" : %s } }\n", + (int)WOLFSENTRY_KV_KEY_LEN(kv_exports), + WOLFSENTRY_KV_KEY(kv_exports), + val_type, + val_buf); + } + ++n_seen; + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->user_values->header.n_ents); + } +#endif + + { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr,"\177\0\0\1",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\177\0\0\1",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + } + + { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + + remote.sa.sa_family = local.sa.sa_family = WOLFSENTRY_AF_CHAOS; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr,"\177\0\0\1",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\177\0\0\1",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_FALLTHROUGH)); + } + + { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 0; + local.sa.sa_port = 13579; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 0; + memcpy(remote.sa.addr,"\1\2\3\4",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\0\0\0\0",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)); + } + +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +/* test bitmap-based matching of CAN addresses. */ + { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + + remote.sa.sa_family = local.sa.sa_family = WOLFSENTRY_AF_CAN; + remote.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE;; + local.sa.addr_len = 0; + remote.sa.interface = local.sa.interface = 0; + memcpy(remote.sa.addr,"\x1f\xff\xff\xed",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER0)); + + memcpy(remote.sa.addr,"\x1f\xff\xff\xec",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER0)); + + memcpy(remote.sa.addr,"\x00\x00\x07\xcb",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER1)); + + memcpy(remote.sa.addr,"\x1f\xff\xff\xff",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER2)); + + memcpy(remote.sa.addr,"\x1f\xff\x01\x23",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER3)); + + /* one prefix-matched CAN address to try */ + memcpy(remote.sa.addr,"\x15\x67\x01\x22",sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER4)); + } +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + { + unsigned char *test_json_document = NULL; + int fd = -1; + JSON_VALUE p_root; + JSON_VALUE *v1 = NULL, *v2 = NULL, *v3 = NULL; + struct stat st; + static const JSON_CONFIG centijson_config = { + 65536, /* max_total_len */ + 1000, /* max_total_values */ + 20, /* max_number_len */ + WOLFSENTRY_KV_MAX_VALUE_BYTES, /* max_string_len */ + WOLFSENTRY_MAX_LABEL_BYTES, /* max_key_len */ + 10, /* max_nesting_level */ + JSON_NOSCALARROOT /* flags */ + }; + JSON_INPUT_POS json_pos; + const unsigned char *s; + size_t alen, i; + + WOLFSENTRY_EXIT_ON_SYSFAILURE(fd = open(fname, O_RDONLY)); + WOLFSENTRY_EXIT_ON_SYSFAILURE(fstat(fd, &st)); + WOLFSENTRY_EXIT_ON_SYSFALSE((test_json_document = (unsigned char *)malloc((size_t)st.st_size)) != NULL); + WOLFSENTRY_EXIT_ON_SYSFALSE(read(fd, test_json_document, (size_t)st.st_size) == st.st_size); + + if ((ret = json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), test_json_document, (size_t)st.st_size, ¢ijson_config, + 0 /* dom_flags */, &p_root, &json_pos)) < 0) { + const void *p = memchr((const char *)(test_json_document + json_pos.offset), '\n', (size_t)st.st_size - json_pos.offset); + int linelen = p ? ((int)((unsigned char *)p - (test_json_document + json_pos.offset)) + (int)json_pos.column_number - 1) : + (((int)st.st_size - (int)json_pos.offset) + (int)json_pos.column_number - 1); + if (WOLFSENTRY_ERROR_DECODE_SOURCE_ID(ret) == WOLFSENTRY_SOURCE_ID_UNSET) + (void)fprintf(stderr, "json_dom_parse failed at offset " SIZET_FMT ", L%u, col %u, with centijson code %d: %s\n", json_pos.offset,json_pos.line_number, json_pos.column_number, ret, json_dom_error_str(ret)); + else + (void)fprintf(stderr, "json_dom_parse failed at offset " SIZET_FMT ", L%u, col %u, with " WOLFSENTRY_ERROR_FMT "\n", json_pos.offset,json_pos.line_number, json_pos.column_number, WOLFSENTRY_ERROR_FMT_ARGS(ret)); + (void)fprintf(stderr,"%.*s\n", linelen, test_json_document + json_pos.offset - json_pos.column_number + 1); + exit(1); + } + + WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "wolfsentry-config-version")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_uint32(v1) == 1U); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v1))); + + WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "default-policies")) == NULL); + WOLFSENTRY_EXIT_ON_TRUE((v2 = json_value_path(v1, "default-policy")) == NULL); + WOLFSENTRY_EXIT_ON_TRUE((s = json_value_string(v2)) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(strcmp((const char *)s, "reject") == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); + + WOLFSENTRY_EXIT_ON_TRUE((v2 = json_value_path(v1, "default-event")) == NULL); + WOLFSENTRY_EXIT_ON_TRUE((s = json_value_string(v2)) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(strcmp((const char *)s, "static-route-parent") == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); + v2 = NULL; + + WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "static-routes-insert")) == NULL); + WOLFSENTRY_EXIT_ON_TRUE((alen = json_value_array_size(v1)) == 0); + for (i = 0; i < alen; ++i) { + WOLFSENTRY_EXIT_ON_TRUE((v2 = json_value_array_get(v1, i)) == NULL); + v3 = json_value_path(v2, "family"); + if (v3) { + WOLFSENTRY_EXIT_ON_TRUE((json_value_string(v3) == NULL) && (json_value_int32(v3) <= 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v3))); + v3 = NULL; + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); + v2 = NULL; + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v1))); + + WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "user-values/user-null")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(v1) == JSON_VALUE_NULL); + + if (v3) + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v3))); + if (v2) + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); + if (v1) + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v1))); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), &p_root))); + if (test_json_document != NULL) + free(test_json_document); + if (fd != -1) + (void)close(fd); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* A duplicate key whose value is a container, parsed with USEFIRST, must + * leave the DOM nesting intact: the first value wins and the discarded + * container must not leak its children into the enclosing object. */ + { + static const char dupkey_doc[] = + "{ \"k\" : 1, \"k\" : { \"x\" : 2 }, \"after\" : 3 }"; + static const JSON_CONFIG dupkey_config = { + 65536, 1000, 20, WOLFSENTRY_KV_MAX_VALUE_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT + }; + JSON_VALUE dupkey_root; + const JSON_VALUE *dupkey_v; + JSON_INPUT_POS dupkey_pos; + + WOLFSENTRY_EXIT_ON_TRUE( + json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)dupkey_doc, strlen(dupkey_doc), &dupkey_config, + JSON_DOM_DUPKEY_USEFIRST, &dupkey_root, &dupkey_pos) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&dupkey_root) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&dupkey_root) == 2); + WOLFSENTRY_EXIT_ON_TRUE((dupkey_v = json_value_path(&dupkey_root, "k")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupkey_v) == 1); + WOLFSENTRY_EXIT_ON_TRUE((dupkey_v = json_value_path(&dupkey_root, "after")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupkey_v) == 3); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupkey_root, "x") == NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate( + json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), &dupkey_root))); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* USEFIRST must also drop duplicate containers whose value is a nested + * object or an array: the first value wins, the duplicate (and anything + * nested in it) is dropped whole, and the surrounding keys stay intact. */ + { + static const char dupnest_doc[] = + "{ \"k\" : { \"a\" : 1 }," + " \"k\" : { \"b\" : { \"c\" : 2 } }," + " \"arr\" : [ 10, 20 ]," + " \"arr\" : [ 30, 40, 50 ]," + " \"after\" : 7 }"; + static const JSON_CONFIG dupnest_config = { + 65536, 1000, 20, WOLFSENTRY_KV_MAX_VALUE_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT + }; + JSON_VALUE dupnest_root; + const JSON_VALUE *dupnest_v; + JSON_INPUT_POS dupnest_pos; + + WOLFSENTRY_EXIT_ON_TRUE( + json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)dupnest_doc, strlen(dupnest_doc), &dupnest_config, + JSON_DOM_DUPKEY_USEFIRST, &dupnest_root, &dupnest_pos) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&dupnest_root) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&dupnest_root) == 3); + + /* first (object) value of "k" wins; the nested duplicate is dropped whole */ + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "k")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(dupnest_v) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(dupnest_v) == 1); + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "k/a")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupnest_v) == 1); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupnest_root, "k/b") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupnest_root, "b") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupnest_root, "c") == NULL); + + /* first (array) value of "arr" wins; the longer duplicate is dropped */ + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "arr")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(dupnest_v) == JSON_VALUE_ARRAY); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(dupnest_v) == 2); + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_get(dupnest_v, 0) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(dupnest_v, 0)) == 10); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(dupnest_v, 1)) == 20); + + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "after")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupnest_v) == 7); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate( + json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), &dupnest_root))); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* A custom allocator must receive its own context pointer, not the + * allocator object, as the first argument to its callbacks. */ + { + static const char ctx_doc[] = "{ \"a\" : [ 1, 2, 3 ] }"; + static const JSON_CONFIG ctx_config = { + 65536, 1000, 20, WOLFSENTRY_KV_MAX_VALUE_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT + }; + struct wolfsentry_allocator ctx_allocator; + JSON_VALUE ctx_root; + JSON_INPUT_POS ctx_pos; + + ctx_allocator.context = &test_dom_allocator_marker; + ctx_allocator.malloc = test_dom_allocator_malloc; + ctx_allocator.free = test_dom_allocator_free; + ctx_allocator.realloc = test_dom_allocator_realloc; + ctx_allocator.memalign = NULL; + ctx_allocator.free_aligned = NULL; + + test_dom_allocator_context_ok = 1; + test_dom_allocator_malloc_calls = 0; + + WOLFSENTRY_EXIT_ON_TRUE( + json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_allocator), + (const unsigned char *)ctx_doc, strlen(ctx_doc), &ctx_config, + 0, &ctx_root, &ctx_pos) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(test_dom_allocator_malloc_calls > 0); + WOLFSENTRY_EXIT_ON_FALSE(test_dom_allocator_context_ok == 1); + + WOLFSENTRY_EXIT_ON_TRUE( + json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_allocator), &ctx_root) < 0); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + + + /* test aux event, flag bit filters & frobbing, and good/bad route insertion by + * wolfsentry_builtin_action_track_peer() + */ + { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *ephemeral_route; + wolfsentry_route_flags_t ephemeral_route_flags; + int i; + + /* first, trigger insertion of DNS dynamic pinhole. */ + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_UDP; + remote.sa.sa_port = 53; + local.sa.sa_port = 65432; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 0; + memcpy(remote.sa.addr,"\1\2\3\4",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\0\0\0\0",sizeof local.addr_buf); + + action_results = WOLFSENTRY_ACTION_RES_USER_BASE | (WOLFSENTRY_ACTION_RES_USER_BASE << 1U) | (WOLFSENTRY_ACTION_RES_USER_BASE << 3U); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_USER_BASE | (WOLFSENTRY_ACTION_RES_USER_BASE << 1U) | (WOLFSENTRY_ACTION_RES_USER_BASE << 3U))); + + /* next, make sure the dynamic pinhole is live, by checking for an exact match. */ + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_USER5)); + WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD); + + /* make sure the pinhole route has the expected flags on it. */ + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(ephemeral_route, &ephemeral_route_flags)); + /* make sure it's really ephemeral. */ + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route->meta.purge_after != 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_flags == + (WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT | + WOLFSENTRY_ROUTE_FLAG_IN_TABLE | + WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED | + WOLFSENTRY_ROUTE_FLAG_GREENLISTED)); + + /* now trigger insertion of a tracking rule to catch a simulated port scan. */ + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 1234; + local.sa.sa_port = 65432; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 0; + memcpy(remote.sa.addr,"\1\2\3\4",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\0\0\0\0",sizeof local.addr_buf); + + action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_UNREACHABLE | (WOLFSENTRY_ACTION_RES_USER_BASE << 4U))); + + /* iteratively increment the derog-thresh-for-penalty-boxing. */ + for (i=0; i<3; ++i) { + action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); + } + + /* trigger the penalty boxing. */ + action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_UPDATE | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); + + /* confirm the penalty boxing. */ + action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); + + +#ifdef WOLFSENTRY_UNITTEST_BENCHMARKS + { + struct timespec start_at, end_at; + uint64_t start_at_cycles, end_at_cycles; + double ns_per_call, cycles_per_call; + + test_action_enabled = 0; + + WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &start_at)); + start_at_cycles = get_intel_cycles(); + for (i=0; i<1000000; ++i) { + action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_table_dump_json_end( + wolfsentry_route_event_dispatch_with_inited_result( WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out2), - WOLFSENTRY_BYTE_STREAM_SPC(json_out2), - WOLFSENTRY_FORMAT_FLAG_NONE)); - break; + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); } + end_at_cycles = get_intel_cycles(); + WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &end_at)); + ns_per_call = ((double)(end_at.tv_sec - start_at.tv_sec) * 1000000000.0 + (double)(end_at.tv_nsec - start_at.tv_nsec)) / (double)i; + cycles_per_call = (double)(end_at_cycles - start_at_cycles) / (double)i; + printf("benchmark wolfsentry_route_event_dispatch_with_inited_result() with JSON-loaded route table, matching penalty-boxed route: %.2f ns/call %.2f cycles/call\n", ns_per_call, cycles_per_call); + +#ifdef WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED + if (cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED) { + (void)fprintf(stderr, "benchmark wolfsentry_route_event_dispatch_with_inited_result() with JSON-loaded route table, matching penalty-boxed route: measured %.2f cycles/call exceeds max %.2f\n", cycles_per_call, (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED); + WOLFSENTRY_EXIT_ON_TRUE(cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED); + } +#endif + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); + + test_action_enabled = 1; } +#endif /* WOLFSENTRY_UNITTEST_BENCHMARKS */ + + + /* force release from penalty box with explicit _RES_COMMENDABLE. + * note that the result will still be _REJECT because of the fallthrough policy in test-config.json. + * in practice, port scanning defenses are only needed on configurations that default open, in which + * case release from the penalty box would restore fallthrough to _ACCEPT. + * on the other hand, where port scanning is being tracked purely for purposes of IDS-type visibility, + * fallthrough to _REJECT is perfectly appropriate. + */ + action_results = WOLFSENTRY_ACTION_RES_COMMENDABLE; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_COMMENDABLE | WOLFSENTRY_ACTION_RES_UPDATE | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_USER5)); + + /* recheck to confirm no update on a second match with zero initial action_results. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_USER5)); + + { + struct wolfsentry_route_exports ephemeral_route_exports; + wolfsentry_ent_id_t reinsert_id; + wolfsentry_time_t purge_after_after_deletion; + + /* increment connection_count. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + DEFERRED, + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* trigger_label */, + 0 /* trigger_label_len */, + &action_results)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_BYTE_STREAM_LEN(json_out2) == WOLFSENTRY_BYTE_STREAM_LEN(json_out)); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(json_out, json_out2, WOLFSENTRY_BYTE_STREAM_LEN(json_out)) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); + purge_after_after_deletion = ephemeral_route_exports.meta.purge_after; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - ret = wolfsentry_config_json_oneshot( - WOLFSENTRY_CONTEXT_ARGS_OUT, - WOLFSENTRY_BYTE_STREAM_HEAD(json_out), - WOLFSENTRY_BYTE_STREAM_LEN(json_out), - WOLFSENTRY_CONFIG_LOAD_FLAG_FLUSH_ONLY_ROUTES, - err_buf, - sizeof err_buf); - if (ret < 0) { - (void)fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); - WOLFSENTRY_EXIT_ON_FAILURE(ret); - } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_reset_metadata_exports(&ephemeral_route_exports)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + ALREADY_OK, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &ephemeral_route_exports, + &reinsert_id, + &action_results)); - WOLFSENTRY_BYTE_STREAM_RESET(json_out2); + WOLFSENTRY_EXIT_ON_FALSE(reinsert_id == id); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_table_dump_json_start( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out2), - WOLFSENTRY_BYTE_STREAM_SPC(json_out2), - WOLFSENTRY_FORMAT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - for (;;) { - ret = wolfsentry_route_table_dump_json_next( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out2), - WOLFSENTRY_BYTE_STREAM_SPC(json_out2), - WOLFSENTRY_FORMAT_FLAG_NONE); - if (ret < 0) { - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_table_dump_json_end( - WOLFSENTRY_CONTEXT_ARGS_OUT, - main_routes, - &cursor, - WOLFSENTRY_BYTE_STREAM_PTR(json_out2), - WOLFSENTRY_BYTE_STREAM_SPC(json_out2), - WOLFSENTRY_FORMAT_FLAG_NONE)); - break; - } - } + WOLFSENTRY_EXIT_ON_TRUE(ephemeral_route_exports.meta.purge_after == purge_after_after_deletion); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_exports.meta.connection_count == 1); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_BYTE_STREAM_LEN(json_out2) == WOLFSENTRY_BYTE_STREAM_LEN(json_out)); - WOLFSENTRY_EXIT_ON_FALSE(memcmp(json_out, json_out2, WOLFSENTRY_BYTE_STREAM_LEN(json_out)) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_purge_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + ephemeral_route, + 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->routes->header.n_ents); + /* decrement connection_count. */ + action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + "call-in-from-unit-test", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + (void *)0x12345678 /* caller_arg */, + &id, + &inexact_matches, + &action_results)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_BYTE_STREAM_FREE_HEAP(json_out2); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_exports.meta.purge_after == 0); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_exports.meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_purge_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + ephemeral_route, + 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + } } - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT, fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - { - struct wolfsentry_context *ctx_clone; + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)); - WOLFSENTRY_EXIT_ON_FAILURE(json_feed_file(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), fname, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, 1)); + WOLFSENTRY_RETURN_OK; +} #ifdef WOLFSENTRY_HAVE_JSON_DOM - { - static const char *sequential_test_json = "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"user-json2\" : { \"json\" : { \"z\" : 26, \"y\" : 25, \"x\" : 24 } } } }"; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_config_json_oneshot( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), - (const unsigned char *)sequential_test_json, - strlen(sequential_test_json), - WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH | WOLFSENTRY_CONFIG_LOAD_FLAG_JSON_DOM_MAINTAINDICTORDER, - NULL, - 0)); - } -#endif +/* Regression coverage for the centijson JSON_VALUE (DOM) public API. Uses a + * controllable test allocator so the error paths can be exercised through the + * public interface. */ - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, ctx_clone)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); +static size_t test_cjv_live = 0; /* count of live allocations */ +static size_t test_cjv_fail_big = 0; /* if nonzero, fail allocations >= this size */ +static int test_cjv_zero_null = 0; /* if set, zero-size requests return NULL */ +static long test_cjv_budget = -1; /* if >= 0, allow this many mallocs then fail */ + +static void test_cjv_reset(void) { + test_cjv_live = 0; + test_cjv_fail_big = 0; + test_cjv_zero_null = 0; + test_cjv_budget = -1; +} + +#ifdef WOLFSENTRY_THREADSAFE +static void *test_cjv_malloc(void *context, struct wolfsentry_thread_context *thread, size_t size) +#else +static void *test_cjv_malloc(void *context, size_t size) +#endif +{ + void *p; + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (test_cjv_zero_null && (size == 0)) + return NULL; + if ((test_cjv_fail_big != 0) && (size >= test_cjv_fail_big)) + return NULL; + if (test_cjv_budget >= 0) { + if (test_cjv_budget == 0) + return NULL; + --test_cjv_budget; } + p = malloc(size); + if (p != NULL) + ++test_cjv_live; + return p; +} - { - struct wolfsentry_context *ctx_clone; +#ifdef WOLFSENTRY_THREADSAFE +static void test_cjv_free(void *context, struct wolfsentry_thread_context *thread, void *ptr) +#else +static void test_cjv_free(void *context, void *ptr) +#endif +{ + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (ptr != NULL) + --test_cjv_live; + free(ptr); +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); +#ifdef WOLFSENTRY_THREADSAFE +static void *test_cjv_realloc(void *context, struct wolfsentry_thread_context *thread, void *ptr, size_t size) +#else +static void *test_cjv_realloc(void *context, void *ptr, size_t size) +#endif +{ + void *p; + int was_null; + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (test_cjv_zero_null && (size == 0)) { + if (ptr != NULL) + --test_cjv_live; + free(ptr); + return NULL; + } + if ((test_cjv_fail_big != 0) && (size >= test_cjv_fail_big)) + return NULL; + was_null = (ptr == NULL); + p = realloc(ptr, size); + if (p != NULL) { + if (was_null) + ++test_cjv_live; + } else if ((size == 0) && (! was_null)) { + --test_cjv_live; } + return p; +} - { - struct wolfsentry_cursor *cursor; - struct wolfsentry_route *route; - struct wolfsentry_route_exports route_exports; - struct wolfsentry_route_table *main_routes; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); - for (ret = wolfsentry_route_table_iterate_current(main_routes, cursor, &route); - ret >= 0; - ret = wolfsentry_route_table_iterate_next(main_routes, cursor, &route)) { - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &route_exports)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_exports_render(WOLFSENTRY_CONTEXT_ARGS_OUT, &route_exports, stdout)); - putc('\n', stdout); - } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); +static int test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE root; + JSON_VALUE *list; + JSON_VALUE *elem; + const JSON_VALUE *found; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); + + list = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root, (const unsigned char *)"list", 4); + WOLFSENTRY_EXIT_ON_TRUE(list == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list) < 0); + + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 10) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 20) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 30) < 0); + + /* Attached array index (key followed immediately by a bracket). */ + found = json_value_path(&root, "list[0]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 10); + + found = json_value_path(&root, "list[2]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 30); + + /* Slash-separated index is a documented equivalent of the attached form. */ + found = json_value_path(&root, "list/[1]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 20); + + /* Negative indexes are relative to the array length. */ + found = json_value_path(&root, "list[-1]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 30); + + found = json_value_path(&root, "list[-3]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 10); + + /* Out-of-range indexes resolve to NULL. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list[3]") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list[-4]") == NULL); + + /* Trailing bytes after a bracket index must be rejected. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list/[0]garbage]") == NULL); + + /* An index that overflows size_t must be rejected, not wrapped. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list/[18446744073709551616]") == NULL); + + /* An empty path returns the root. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "") == &root); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE arr; + JSON_VALUE *elem; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + for (i = 0; i < 3; ++i) { + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, i) < 0); } + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 3); -#ifndef WOLFSENTRY_NO_STDIO - { - struct wolfsentry_kv_pair_internal *kv_ref; - struct wolfsentry_cursor *cursor; - const struct wolfsentry_kv_pair *kv_exports; - const char *val_type; - char val_buf[2048]; - int val_buf_space; - wolfsentry_hitcount_t n_seen = 0; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); - for (ret = wolfsentry_user_values_iterate_current(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref); - ret >= 0; - ret = wolfsentry_user_values_iterate_next(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref)) { - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); - val_buf_space = sizeof val_buf; - if (wolfsentry_kv_type_to_string(WOLFSENTRY_KV_TYPE(kv_exports), &val_type) < 0) - val_type = "?"; - if (wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, val_buf, &val_buf_space) < 0) { - if (WOLFSENTRY_KV_TYPE(kv_exports) == WOLFSENTRY_KV_BYTES) - (void)snprintf(val_buf, sizeof val_buf, "<%.*s>", (int)WOLFSENTRY_KV_V_BYTES_LEN(kv_exports), WOLFSENTRY_KV_V_BYTES(kv_exports)); - else - strcpy(val_buf,"?"); - } - switch (WOLFSENTRY_KV_TYPE(kv_exports)) { - case WOLFSENTRY_KV_TRUE: - case WOLFSENTRY_KV_FALSE: - case WOLFSENTRY_KV_NULL: - printf("{ \"%.*s\" : %s }\n", - (int)WOLFSENTRY_KV_KEY_LEN(kv_exports), - WOLFSENTRY_KV_KEY(kv_exports), - val_buf); - break; - default: - printf("{ \"%.*s\" : { \"%s\" : %s } }\n", - (int)WOLFSENTRY_KV_KEY_LEN(kv_exports), - WOLFSENTRY_KV_KEY(kv_exports), - val_type, - val_buf); - } - ++n_seen; - } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(n_seen == wolfsentry->user_values->header.n_ents); + /* index + count must not wrap past the array size. */ + ret = json_value_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 3, SIZE_MAX); + WOLFSENTRY_EXIT_ON_FALSE(ret == -1); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 3); + + /* a legitimate removal still works. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 1, 1) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 2); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(&arr, 1)) == 2); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + return 0; +} + +static int test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE src; + JSON_VALUE clone; + JSON_VALUE *elem; + JSON_VALUE *inner; + unsigned char small_str[50]; + unsigned char big_str[256]; + int i; + int ret; + + memset(small_str, 'a', sizeof small_str); + memset(big_str, 'b', sizeof big_str); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_string_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, small_str, sizeof small_str) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_string_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, big_str, sizeof big_str) < 0); + + json_value_init_null(&clone); + + /* Force the clone of the second (large) element to fail partway. */ + test_cjv_fail_big = 200; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_fail_big = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* The failed clone must finalize the already-cloned prefix, leaking nothing. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + /* Also exercise failure on a partially-built nested aggregate element: a + * nested array whose element buffer allocation fails after its payload was + * allocated must not leak that payload. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + inner = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(inner == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), inner) < 0); + for (i = 0; i < 16; ++i) { + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), inner); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, i) < 0); + inner = json_value_array_get(&src, 0); + WOLFSENTRY_EXIT_ON_TRUE(inner == NULL); } -#endif - { - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } remote, local; - wolfsentry_route_flags_t inexact_matches; - wolfsentry_action_res_t action_results; + json_value_init_null(&clone); + /* The clone of the 16-slot inner array needs a 256-byte element buffer, + * which fails after the inner array payload has been allocated. */ + test_cjv_fail_big = 200; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_fail_big = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE src; + JSON_VALUE clone; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + json_value_init_null(&clone); - remote.sa.sa_family = local.sa.sa_family = AF_INET; - remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; - remote.sa.sa_port = 12345; - local.sa.sa_port = 443; - remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - remote.sa.interface = local.sa.interface = 1; - memcpy(remote.sa.addr,"\177\0\0\1",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\177\0\0\1",sizeof local.addr_buf); + /* With an allocator that returns NULL for zero-size requests, cloning an + * empty array must still succeed (it needs no element storage). */ + test_cjv_zero_null = 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&clone) == JSON_VALUE_ARRAY); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&clone) == 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); - } + return 0; +} - { - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } remote, local; - wolfsentry_route_flags_t inexact_matches; - wolfsentry_action_res_t action_results; +static int test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE v; + + /* A finite double outside the float range is not float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e308) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small double is float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 2.0) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^24 + 1 is not exactly representable in a float. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int64_t)16777217) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^24 + 2 is exactly representable in a float and must stay compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int64_t)16777218) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small integer is float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int32_t)1000) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^53 + 1 is not exactly representable in a double. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (((uint64_t)1 << 53) + 1)) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^53 + 2 is exactly representable in a double and must stay compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (((uint64_t)1 << 53) + 2)) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small integer is double-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int32_t)5) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); - remote.sa.sa_family = local.sa.sa_family = WOLFSENTRY_AF_CHAOS; - remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; - remote.sa.sa_port = 12345; - local.sa.sa_port = 443; - remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - remote.sa.interface = local.sa.interface = 1; - memcpy(remote.sa.addr,"\177\0\0\1",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\177\0\0\1",sizeof local.addr_buf); + return 0; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); +static int test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE v; + + /* Out-of-range float/double values must not be treated as integer-compatible, + * and the predicate must not perform an out-of-range floating-to-integer cast. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_float(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e30F) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_UINT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e300) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_UINT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* An in-range integral float is integer-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_float(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 42.0F) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A non-integral double is not integer-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1.5) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_FALLTHROUGH)); + return 0; +} + +static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); } - { - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } remote, local; - wolfsentry_route_flags_t inexact_matches; - wolfsentry_action_res_t action_results; + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"b", 1); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)1) < 0); - remote.sa.sa_family = local.sa.sa_family = AF_INET; - remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; - remote.sa.sa_port = 0; - local.sa.sa_port = 13579; - remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - remote.sa.interface = local.sa.interface = 0; - memcpy(remote.sa.addr,"\1\2\3\4",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\0\0\0\0",sizeof local.addr_buf); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 2); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); + /* Removing a nested dictionary needs no allocation, so it succeeds and frees the subtree with none available. */ + test_cjv_budget = 0; + ret = json_value_dict_remove_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 1); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + for (i = 0; i < 3; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); } + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"zz", 2); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)"in", 2); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, 7) < 0); + + /* cleanup allocates nothing, so the earlier keys and the nested dictionary are all freed with no budget */ + test_cjv_budget = 0; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); -#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING -/* test bitmap-based matching of CAN addresses. */ - { - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } remote, local; - wolfsentry_route_flags_t inexact_matches; - wolfsentry_action_res_t action_results; + return 0; +} - remote.sa.sa_family = local.sa.sa_family = WOLFSENTRY_AF_CAN; - remote.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE;; - local.sa.addr_len = 0; - remote.sa.interface = local.sa.interface = 0; - memcpy(remote.sa.addr,"\x1f\xff\xff\xed",sizeof remote.addr_buf); +static int test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER0)); + /* Cleanup allocates nothing, so it must succeed and free every node even + * with no allocation budget left. */ + test_cjv_budget = 0; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); - memcpy(remote.sa.addr,"\x1f\xff\xff\xec",sizeof remote.addr_buf); + return 0; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); +static int test_cjvalue_nested_cleanup_no_leak(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE clone; + JSON_VALUE *nested; + JSON_VALUE *arr; + JSON_VALUE *slot; + JSON_VALUE *member; + char keybuf[8]; + size_t live_before; + long budget; + int i; + int ret; - WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER0)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + arr = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"b", 1); + WOLFSENTRY_EXIT_ON_TRUE(arr == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), arr) < 0); + slot = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), arr); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + member = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(member == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), member, (int32_t)i) < 0); + member = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(member == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), member, (int32_t)i) < 0); + } - memcpy(remote.sa.addr,"\x00\x00\x07\xcb",sizeof remote.addr_buf); + /* Cloning the array must release everything it built at every possible allocation failure. */ + live_before = test_cjv_live; + for (budget = 0; ; ++budget) { + json_value_init_null(&clone); + test_cjv_budget = budget; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), arr, &clone); + test_cjv_budget = -1; + if (ret == 0) { + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == live_before); + break; + } + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == live_before); + WOLFSENTRY_EXIT_ON_TRUE(budget > 64); + } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); + /* Removing a nested dictionary must free it even with no allocation budget. */ + test_cjv_budget = 0; + ret = json_value_dict_remove(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a"); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER1)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); - memcpy(remote.sa.addr,"\x1f\xff\xff\xff",sizeof remote.addr_buf); + return 0; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); +static int test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE d; + JSON_VALUE clone; + int ret; - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER2)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d) < 0); + json_value_init_null(&clone); - memcpy(remote.sa.addr,"\x1f\xff\x01\x23",sizeof remote.addr_buf); + /* Cloning an empty dictionary must not require a zero-size allocation. */ + test_cjv_zero_null = 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d, &clone); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&clone) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); + /* Cleaning up an empty dictionary must not require a zero-size allocation. */ + test_cjv_zero_null = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER3)); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); - /* one prefix-matched CAN address to try */ - memcpy(remote.sa.addr,"\x15\x67\x01\x22",sizeof remote.addr_buf); + return 0; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, &action_results)); +static int test_cjvalue_array_shrink_to_zero(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE arr; + JSON_VALUE *elem; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, (int32_t)7) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 1); + + /* Model an allocator for which realloc(ptr, 0) frees and returns NULL. + * Removing the last element shrinks the backing store to zero capacity; the + * array must stay usable (no dangling buffer) and finalize cleanly. */ + test_cjv_zero_null = 1; + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_remove(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 0) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 0); + + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, (int32_t)9) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(&arr, 0)) == 9); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static wolfsentry_errcode_t test_cjvalue_fixes(void) { + struct wolfsentry_allocator test_allocator; + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&test_allocator, 0, sizeof test_allocator); + test_allocator.malloc = test_cjv_malloc; + test_allocator.free = test_cjv_free; + test_allocator.realloc = test_cjv_realloc; + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_USER4)); - } -#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); -#ifdef WOLFSENTRY_HAVE_JSON_DOM - { - unsigned char *test_json_document = NULL; - int fd = -1; - JSON_VALUE p_root; - JSON_VALUE *v1 = NULL, *v2 = NULL, *v3 = NULL; - struct stat st; - static const JSON_CONFIG centijson_config = { - 65536, /* max_total_len */ - 1000, /* max_total_values */ - 20, /* max_number_len */ - WOLFSENTRY_KV_MAX_VALUE_BYTES, /* max_string_len */ - WOLFSENTRY_MAX_LABEL_BYTES, /* max_key_len */ - 10, /* max_nesting_level */ - JSON_NOSCALARROOT /* flags */ - }; - JSON_INPUT_POS json_pos; - const unsigned char *s; - size_t alen, i; + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_SYSFAILURE(fd = open(fname, O_RDONLY)); - WOLFSENTRY_EXIT_ON_SYSFAILURE(fstat(fd, &st)); - WOLFSENTRY_EXIT_ON_SYSFALSE((test_json_document = (unsigned char *)malloc((size_t)st.st_size)) != NULL); - WOLFSENTRY_EXIT_ON_SYSFALSE(read(fd, test_json_document, (size_t)st.st_size) == st.st_size); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - if ((ret = json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), test_json_document, (size_t)st.st_size, ¢ijson_config, - 0 /* dom_flags */, &p_root, &json_pos)) < 0) { - const void *p = memchr((const char *)(test_json_document + json_pos.offset), '\n', (size_t)st.st_size - json_pos.offset); - int linelen = p ? ((int)((unsigned char *)p - (test_json_document + json_pos.offset)) + (int)json_pos.column_number - 1) : - (((int)st.st_size - (int)json_pos.offset) + (int)json_pos.column_number - 1); - if (WOLFSENTRY_ERROR_DECODE_SOURCE_ID(ret) == WOLFSENTRY_SOURCE_ID_UNSET) - (void)fprintf(stderr, "json_dom_parse failed at offset " SIZET_FMT ", L%u, col %u, with centijson code %d: %s\n", json_pos.offset,json_pos.line_number, json_pos.column_number, ret, json_dom_error_str(ret)); - else - (void)fprintf(stderr, "json_dom_parse failed at offset " SIZET_FMT ", L%u, col %u, with " WOLFSENTRY_ERROR_FMT "\n", json_pos.offset,json_pos.line_number, json_pos.column_number, WOLFSENTRY_ERROR_FMT_ARGS(ret)); - (void)fprintf(stderr,"%.*s\n", linelen, test_json_document + json_pos.offset - json_pos.column_number + 1); - exit(1); - } + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "wolfsentry-config-version")) == NULL); - WOLFSENTRY_EXIT_ON_FALSE(json_value_uint32(v1) == 1U); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v1))); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "default-policies")) == NULL); - WOLFSENTRY_EXIT_ON_TRUE((v2 = json_value_path(v1, "default-policy")) == NULL); - WOLFSENTRY_EXIT_ON_TRUE((s = json_value_string(v2)) == NULL); - WOLFSENTRY_EXIT_ON_FALSE(strcmp((const char *)s, "reject") == 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_nested_cleanup_no_leak(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_TRUE((v2 = json_value_path(v1, "default-event")) == NULL); - WOLFSENTRY_EXIT_ON_TRUE((s = json_value_string(v2)) == NULL); - WOLFSENTRY_EXIT_ON_FALSE(strcmp((const char *)s, "static-route-parent") == 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); - v2 = NULL; + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "static-routes-insert")) == NULL); - WOLFSENTRY_EXIT_ON_TRUE((alen = json_value_array_size(v1)) == 0); - for (i = 0; i < alen; ++i) { - WOLFSENTRY_EXIT_ON_TRUE((v2 = json_value_array_get(v1, i)) == NULL); - v3 = json_value_path(v2, "family"); - if (v3) { - WOLFSENTRY_EXIT_ON_TRUE((json_value_string(v3) == NULL) && (json_value_int32(v3) <= 0)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v3))); - v3 = NULL; - } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); - v2 = NULL; - } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v1))); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); - WOLFSENTRY_EXIT_ON_TRUE((v1 = json_value_path(&p_root, "user-values/user-null")) == NULL); - WOLFSENTRY_EXIT_ON_FALSE(json_value_type(v1) == JSON_VALUE_NULL); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_shrink_to_zero(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} - if (v3) - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v3))); - if (v2) - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v2))); - if (v1) - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), v1))); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), &p_root))); - if (test_json_document != NULL) - free(test_json_document); - if (fd != -1) - (void)close(fd); - } #endif /* WOLFSENTRY_HAVE_JSON_DOM */ +static wolfsentry_errcode_t test_json_oneshot_string( + const char *json, + wolfsentry_config_load_flags_t load_flags) +{ + wolfsentry_errcode_t ret, load_ret; + struct wolfsentry_context *wolfsentry; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - /* test aux event, flag bit filters & frobbing, and good/bad route insertion by - * wolfsentry_builtin_action_track_peer() + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + load_ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, + strlen(json), + load_flags, + NULL, + 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + WOLFSENTRY_ERROR_RERETURN(load_ret); +} + +static int test_json_uint64_negative(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"v\" : { \"uint\" : -1 } } }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +#ifndef WOLFSENTRY_NO_GETPROTOBY +static int test_json_unknown_service_name(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"protocol\" : 6, \"remote\" : { \"port\" : \"no-such-service-zzz\" } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_json_unknown_protocol_name(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"protocol\" : \"no-such-protocol-zzz\" } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* !WOLFSENTRY_NO_GETPROTOBY */ + +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static int test_json_bitmask_short_address(void) { + /* prefix-bits before a bitmask would right-justify only the prefix-sized + * leading bytes of a full-width address, so the combination is refused, + * as prefix-bits after a bitmask already is; address then bitmask loads. */ - { - struct { - struct wolfsentry_sockaddr sa; - byte addr_buf[4]; - } remote, local; - wolfsentry_route_flags_t inexact_matches; - wolfsentry_action_res_t action_results; - struct wolfsentry_route *ephemeral_route; - wolfsentry_route_flags_t ephemeral_route_flags; - int i; + static const char json_prefixed[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"prefix-bits\" : 16, \"address\" : \"1.2.0.0\", \"bitmask\" : \"255.255.0.0\" } } ] }"; + static const char json_plain[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"address\" : \"1.2.0.0\", \"bitmask\" : \"255.255.0.0\" } } ] }"; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + CONFIG_MISPLACED_KEY, + test_json_oneshot_string(json_prefixed, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json_plain, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ - /* first, trigger insertion of DNS dynamic pinhole. */ +static int test_json_af_local_address(void) { + /* family 1 == WOLFSENTRY_AF_LOCAL; the loader must parse the path address + * that the formatter emits, keeping dump/reload symmetric. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/tmp/sock\" } } ] }"; + /* the formatter dumps AF_LOCAL routes with an explicit prefix-bits; that + * dumped form must reload, which requires the loader to accept prefix-bits + * for AF_LOCAL ("/tmp/sock" is 9 bytes == 72 bits). + */ + static const char json_dump_form[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/tmp/sock\", \"prefix-bits\" : 72 } } ] }"; + /* a path containing a JSON-special byte would dump unescaped and corrupt + * the output, so it must be rejected at load. + */ + static const char json_unsafe[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/tmp/a\\\"b\" } } ] }"; + /* a prefix-bits inconsistent with the pathname length (here 2 bytes == 16 + * bits) would leave implicit trailing bytes, so it must be rejected in + * either key order; a non-byte-aligned prefix must also be rejected. + */ + static const char json_overlong_prefix[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/x\", \"prefix-bits\" : 128 } } ] }"; + static const char json_prefix_first[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"prefix-bits\" : 128, \"address\" : \"/x\" } } ] }"; + static const char json_unaligned_prefix[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/x\", \"prefix-bits\" : 12 } } ] }"; + /* an empty path or a zero prefix would make a zero-length, match-all + * address with the wildcard flag cleared; reject both in either order. + */ + static const char json_empty_path[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"\" } } ] }"; + static const char json_empty_path_then_prefix[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"\", \"prefix-bits\" : 128 } } ] }"; + static const char json_prefix_then_empty_path[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"prefix-bits\" : 128, \"address\" : \"\" } } ] }"; + static const char json_zero_prefix_first[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"prefix-bits\" : 0, \"address\" : \"/x\" } } ] }"; + static const char json_zero_prefix_last[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/x\", \"prefix-bits\" : 0 } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_empty_path, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_empty_path_then_prefix, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_prefix_then_empty_path, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_zero_prefix_first, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_zero_prefix_last, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json_dump_form, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_unsafe, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_overlong_prefix, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_prefix_first, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_unaligned_prefix, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - remote.sa.sa_family = local.sa.sa_family = AF_INET; - remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_UDP; - remote.sa.sa_port = 53; - local.sa.sa_port = 65432; - remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - remote.sa.interface = local.sa.interface = 0; - memcpy(remote.sa.addr,"\1\2\3\4",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\0\0\0\0",sizeof local.addr_buf); +static int test_json_failed_load_fails_closed(void) { + wolfsentry_errcode_t ret; + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t default_policy = WOLFSENTRY_ACTION_RES_NONE; + static const char good[] = + "{ \"wolfsentry-config-version\" : 1, \"default-policies\" : { \"default-policy\" : \"accept\" } }"; + static const char bad[] = + "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"x\" : { \"uint\" : -1 } } }"; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - action_results = WOLFSENTRY_ACTION_RES_USER_BASE | (WOLFSENTRY_ACTION_RES_USER_BASE << 1U) | (WOLFSENTRY_ACTION_RES_USER_BASE << 3U); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)good, strlen(good), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_USER_BASE | (WOLFSENTRY_ACTION_RES_USER_BASE << 1U) | (WOLFSENTRY_ACTION_RES_USER_BASE << 3U))); + /* a failing default-mode load must leave the context fail-closed. */ + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)bad, strlen(bad), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); - /* next, make sure the dynamic pinhole is live, by checking for an exact match. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_get(WOLFSENTRY_CONTEXT_ARGS_OUT, &default_policy)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(default_policy == WOLFSENTRY_ACTION_RES_REJECT); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_USER5)); - WOLFSENTRY_EXIT_ON_FALSE(inexact_matches == WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD); +static int test_json_actions_update_rejected(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"actions-update\" : [ ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - /* make sure the pinhole route has the expected flags on it. */ +static int test_json_prefix_bits_overbound(void) { + /* prefix-bits beyond the family width must be rejected before the value can + * drive an out-of-bounds copy from the fixed staging buffer. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"address\" : \"1.2.3.4\", \"prefix-bits\" : 65535 } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(ephemeral_route, &ephemeral_route_flags)); - /* make sure it's really ephemeral. */ - WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route->meta.purge_after != 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_flags == - (WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD | - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | - WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT | - WOLFSENTRY_ROUTE_FLAG_IN_TABLE | - WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED | - WOLFSENTRY_ROUTE_FLAG_GREENLISTED)); +static int test_json_link64_short_address(void) { + /* family 119 == WOLFSENTRY_AF_LINK64; a six-byte EUI-48 address must not be + * silently accepted as LINK64. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 119, \"remote\" : { \"address\" : \"00:11:22:33:44:55\" }, \"local\" : { \"address\" : \"00:11:22:33:44:55\" } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - /* now trigger insertion of a tracking rule to catch a simulated port scan. */ +static int test_json_duration_negative(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"config-update\" : { \"penalty-box-duration\" : \"-100s\" } }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - remote.sa.sa_family = local.sa.sa_family = AF_INET; - remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; - remote.sa.sa_port = 1234; - local.sa.sa_port = 65432; - remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; - remote.sa.interface = local.sa.interface = 0; - memcpy(remote.sa.addr,"\1\2\3\4",sizeof remote.addr_buf); - memcpy(local.sa.addr,"\0\0\0\0",sizeof local.addr_buf); +static int test_json_uint8_hex_interface(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"interface\" : \"0xff\" } } ] }"; + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; +static int test_json_uint16_bounds(void) { + static const char too_big[] = + "{ \"wolfsentry-config-version\" : 1, \"events\" : [ { \"label\" : \"e\", \"priority\" : 70000 } ] }"; + static const char at_max[] = + "{ \"wolfsentry-config-version\" : 1, \"events\" : [ { \"label\" : \"e\", \"priority\" : 65535 } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(too_big, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(at_max, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); +static int test_json_load_one_route_flags( + const char *json, + wolfsentry_route_flags_t *route_flags) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_UNREACHABLE | (WOLFSENTRY_ACTION_RES_USER_BASE << 4U))); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); - /* iteratively increment the derog-thresh-for-penalty-boxing. */ - for (i=0; i<3; ++i) { - action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, strlen(json), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_current(main_routes, cursor, &route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, route_flags)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); - } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + WOLFSENTRY_RETURN_OK; +} - /* trigger the penalty boxing. */ - action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; +static int test_json_tcplike_explicit_flag(void) { + /* family 2 == INET, protocol 6 == TCP; the flag key precedes "protocol", + * as in the route dump, so an explicit setting must survive the protocol + * clause's implicit default. + */ + static const char explicit_false[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"tcplike-port-numbers\" : false, \"protocol\" : 6 } ] }"; + static const char explicit_true[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"tcplike-port-numbers\" : true, \"protocol\" : 6 } ] }"; + static const char omitted[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"protocol\" : 6 } ] }"; + wolfsentry_route_flags_t route_flags = WOLFSENTRY_ROUTE_FLAG_NONE; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_load_one_route_flags(explicit_false, &route_flags), OK)); + WOLFSENTRY_EXIT_ON_FALSE(! WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_UPDATE | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); + route_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_load_one_route_flags(explicit_true, &route_flags), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)); - /* confirm the penalty boxing. */ - action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; + route_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_load_one_route_flags(omitted, &route_flags), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); +static int test_json_link_prefix_bits_key_order(void) { + static const char fmt[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : %u," + " \"remote\" : { %s }, \"local\" : { \"address\" : \"%s\" } } ] }"; + static const char eui64[] = "00:11:22:33:44:55:66:77"; + static const char eui48[] = "00:11:22:33:44:55"; + char json[512]; + int json_len; + + /* generic LINK resolves to EUI-48 or EUI-64 from the address, so a + * prefix-bits key must be judged the same before or after the address. */ + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"prefix-bits\" : 64, \"address\" : \"00:11:22:33:44:55:66:77\"", eui64); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), OK)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"address\" : \"00:11:22:33:44:55:66:77\", \"prefix-bits\" : 64", eui64); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), OK)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"prefix-bits\" : 64, \"address\" : \"00:11:22:33:44:55\"", eui48); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), NUMERIC_ARG_TOO_BIG)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"address\" : \"00:11:22:33:44:55\", \"prefix-bits\" : 64", eui48); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), NUMERIC_ARG_TOO_BIG)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"prefix-bits\" : 48, \"address\" : \"00:11:22:33:44:55\"", eui48); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), OK)); + WOLFSENTRY_RETURN_OK; +} -#ifdef WOLFSENTRY_UNITTEST_BENCHMARKS - { - struct timespec start_at, end_at; - uint64_t start_at_cycles, end_at_cycles; - double ns_per_call, cycles_per_call; +static int af_local_custom_parser_calls; - test_action_enabled = 0; +static wolfsentry_errcode_t af_local_custom_parser( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *addr_text, + int addr_text_len, + byte *addr_internal, + wolfsentry_addr_bits_t *addr_internal_bits) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)addr_text; + (void)addr_text_len; + ++af_local_custom_parser_calls; + addr_internal[0] = 0x5a; + *addr_internal_bits = 8; + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &start_at)); - start_at_cycles = get_intel_cycles(); - for (i=0; i<1000000; ++i) { - action_results = WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_DEROGATORY; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); - } - end_at_cycles = get_intel_cycles(); - WOLFSENTRY_EXIT_ON_SYSFAILURE(clock_gettime(CLOCK_MONOTONIC, &end_at)); - ns_per_call = ((double)(end_at.tv_sec - start_at.tv_sec) * 1000000000.0 + (double)(end_at.tv_nsec - start_at.tv_nsec)) / (double)i; - cycles_per_call = (double)(end_at_cycles - start_at_cycles) / (double)i; - printf("benchmark wolfsentry_route_event_dispatch_with_inited_result() with JSON-loaded route table, matching penalty-boxed route: %.2f ns/call %.2f cycles/call\n", ns_per_call, cycles_per_call); +static wolfsentry_errcode_t af_local_custom_formatter( + WOLFSENTRY_CONTEXT_ARGS_IN, + const byte *addr_internal, + unsigned int addr_internal_bits, + char *addr_text, + int *addr_text_len) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)addr_internal; + (void)addr_internal_bits; + if (*addr_text_len < 1) + WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); + addr_text[0] = 'z'; + *addr_text_len = 1; + WOLFSENTRY_RETURN_OK; +} -#ifdef WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED - if (cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED) { - (void)fprintf(stderr, "benchmark wolfsentry_route_event_dispatch_with_inited_result() with JSON-loaded route table, matching penalty-boxed route: measured %.2f cycles/call exceeds max %.2f\n", cycles_per_call, (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED); - WOLFSENTRY_EXIT_ON_TRUE(cycles_per_call > (double)WOLFSENTRY_MAX_CYCLES_PER_CALL_JSON_LOADED); - } +static int test_json_af_local_registered_handler(void) { + struct wolfsentry_context *wolfsentry; + char json[256]; + int json_len; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_DEROGATORY | WOLFSENTRY_ACTION_RES_UNREACHABLE | WOLFSENTRY_ACTION_RES_USER5)); + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); - test_action_enabled = 1; - } -#endif /* WOLFSENTRY_UNITTEST_BENCHMARKS */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_LOCAL, + "test_local", + -1, + af_local_custom_parser, + af_local_custom_formatter, + 8)); - /* force release from penalty box with explicit _RES_COMMENDABLE. - * note that the result will still be _REJECT because of the fallthrough policy in test-config.json. - * in practice, port scanning defenses are only needed on configurations that default open, in which - * case release from the penalty box would restore fallthrough to _ACCEPT. - * on the other hand, where port scanning is being tracked purely for purposes of IDS-type visibility, - * fallthrough to _REJECT is perfectly appropriate. - */ - action_results = WOLFSENTRY_ACTION_RES_COMMENDABLE; + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : %u, \"remote\" : { \"address\" : \"anything\" } } ] }", + (unsigned int)WOLFSENTRY_AF_LOCAL); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + af_local_custom_parser_calls = 0; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + NULL, + 0)); + WOLFSENTRY_EXIT_ON_FALSE(af_local_custom_parser_calls == 1); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_COMMENDABLE | WOLFSENTRY_ACTION_RES_UPDATE | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_USER5)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); - /* recheck to confirm no update on a second match with zero initial action_results. */ - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); - WOLFSENTRY_EXIT_ON_FALSE(action_results == (WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_FALLTHROUGH | WOLFSENTRY_ACTION_RES_USER5)); + WOLFSENTRY_RETURN_OK; +} - { - struct wolfsentry_route_exports ephemeral_route_exports; - wolfsentry_ent_id_t reinsert_id; - wolfsentry_time_t purge_after_after_deletion; +struct sax_dump_capture { + char text[128]; + size_t len; +}; - /* increment connection_count. */ - action_results = WOLFSENTRY_ACTION_RES_CONNECT; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); +static int sax_dump_capture_cb(const unsigned char *str, size_t size, void *user_data) { + struct sax_dump_capture *cap = (struct sax_dump_capture *)user_data; + if (size >= sizeof cap->text - cap->len) + return -1; + memcpy(cap->text + cap->len, str, size); + cap->len += size; + cap->text[cap->len] = '\0'; + return 0; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); +static int sax_noop_process_cb(JSON_TYPE type, const unsigned char *data, size_t data_size, void *user_data) { + (void)type; + (void)data; + (void)data_size; + (void)user_data; + return 0; +} - WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( - DEFERRED, - wolfsentry_route_delete_by_id( - WOLFSENTRY_CONTEXT_ARGS_OUT, - NULL /* caller_arg */, - id, - NULL /* trigger_label */, - 0 /* trigger_label_len */, - &action_results)); +static int sax_dump_double_text(double dbl, struct sax_dump_capture *cap) { + cap->len = 0; + cap->text[0] = '\0'; + return json_dump_double(dbl, sax_dump_capture_cb, cap); +} + +static wolfsentry_errcode_t test_json_sax_double_roundtrip(void) { + static const double corpus[] = { + 1.0 + 2.220446049250313e-16, + 0.1 + 0.2, + 0.1, + 1.0 / 3.0, + -2.0 / 3.0, + 1.7976931348623157e308, + 2.2250738585072014e-308, + 4.9406564584124654e-324, + 1e-300, + 123456789.12345679 + }; + struct sax_dump_capture cap; + size_t i; + double reparsed; + + for (i = 0; i < sizeof corpus / sizeof corpus[0]; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(corpus[i], &cap)); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)cap.text, cap.len, &reparsed)); + if (memcmp(&reparsed, &corpus[i], sizeof reparsed) != 0) { + (void)fprintf(stderr, "double did not survive dump and reparse: %s\n", cap.text); + return 1; + } + } + + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); - purge_after_after_deletion = ephemeral_route_exports.meta.purge_after; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); +static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { + double inf; + double reparsed; + struct sax_dump_capture cap; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_reset_metadata_exports(&ephemeral_route_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)"1e999", 5, &inf)); + WOLFSENTRY_EXIT_ON_FALSE(inf > 1.7976931348623157e308); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( - ALREADY_OK, - wolfsentry_route_insert_by_exports( - WOLFSENTRY_CONTEXT_ARGS_OUT, - NULL /* caller_arg */, - &ephemeral_route_exports, - &reinsert_id, - &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(inf, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); - WOLFSENTRY_EXIT_ON_FALSE(reinsert_id == id); + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(-inf, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)"nan", 3, &reparsed)); + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(reparsed, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); - WOLFSENTRY_EXIT_ON_TRUE(ephemeral_route_exports.meta.purge_after == purge_after_after_deletion); - WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_exports.meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(1.0, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "1.0") == 0); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)cap.text, cap.len, &reparsed)); + WOLFSENTRY_EXIT_ON_FALSE(reparsed == 1.0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_purge_time_set( - WOLFSENTRY_CONTEXT_ARGS_OUT, - ephemeral_route, - 0)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_RETURN_OK; +} - /* decrement connection_count. */ - action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT, - &remote.sa, - &local.sa, - WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, - "call-in-from-unit-test", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - (void *)0x12345678 /* caller_arg */, - &id, - &inexact_matches, - &action_results)); +static wolfsentry_errcode_t test_json_sax_number_text_boundary(void) { + unsigned char num[65]; + double result; + size_t len; - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, ephemeral_route, &ephemeral_route_exports)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + memset(num, '0', sizeof num); + num[0] = '1'; - WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_exports.meta.purge_after == 0); - WOLFSENTRY_EXIT_ON_FALSE(ephemeral_route_exports.meta.connection_count == 0); + for (len = 62; len <= 63; ++len) { + result = 0.0; + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double(num, len, &result)); + /* the cast drops x87 excess precision from the constants */ + WOLFSENTRY_EXIT_ON_FALSE(result == (double)((len == 62) ? 1e61 : 1e62)); + } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_ON_FAILURE( - wolfsentry_route_purge_time_set( - WOLFSENTRY_CONTEXT_ARGS_OUT, - ephemeral_route, - 1)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_TRUE(json_number_to_double(num, 64, &result) >= 0); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_RETURN_OK; +} - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); - WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&ephemeral_route)); - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); +static wolfsentry_errcode_t test_json_sax_truncated_input(void) { + static const JSON_CALLBACKS callbacks = { sax_noop_process_cb }; + static const JSON_CONFIG sax_config = { + 4096, /* max_total_len */ + 1000, /* max_total_values */ + 20, /* max_number_len */ + 256, /* max_string_len */ + 64, /* max_key_len */ + 10, /* max_nesting_level */ + 0 /* flags */ + }; + static const char * const truncated[] = { + "t", "tr", "tru", + "f", "fa", "fal", "fals", + "n", "nu", "nul", + "[t", "[fals", "[nul", + "{\"a\":t", "{\"a\":tru", "{\"a\":nu", + "[true", "[false", "[null", "[1", "{\"a\":true", "{\"a\":1", "{\"a\":[null" + }; + static const char * const complete[] = { + "true", "false", "null", "[true]", "[null, false]", "{\"a\":true}" + }; + struct wolfsentry_context *wolfsentry; + size_t i; + JSON_INPUT_POS pos; + int ret; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + for (i = 0; i < sizeof truncated / sizeof truncated[0]; ++i) { + ret = json_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)truncated[i], strlen(truncated[i]), + &callbacks, &sax_config, NULL, &pos); + if (ret >= 0) { + (void)fprintf(stderr, "truncated JSON accepted: %s\n", truncated[i]); + return 1; + } + } + + for (i = 0; i < sizeof complete / sizeof complete[0]; ++i) { + ret = json_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)complete[i], strlen(complete[i]), + &callbacks, &sax_config, NULL, &pos); + if (ret < 0) { + (void)fprintf(stderr, "complete JSON rejected: %s\n", complete[i]); + return 1; } } @@ -5654,99 +14763,418 @@ static int test_json_corpus(void) { scenario = MAP_FAILED; } - if ((ret < 0) && (! ignore_failed_parse)) { - ret = wolfsentry_centijson_errcode_translate(ret); - ret = WOLFSENTRY_ERROR_RECODE(ret); - break; - } - } + if ((ret < 0) && (! ignore_failed_parse)) { + ret = wolfsentry_centijson_errcode_translate(ret); + ret = WOLFSENTRY_ERROR_RECODE(ret); + break; + } + } + + WOLFSENTRY_EXIT_ON_SYSFAILURE(closedir(corpus_dir)); + } while (0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_ERROR_RERETURN(ret); +} + +#endif /* TEST_JSON_CORPUS */ + +#ifdef FREERTOS + +int main(int argc, char* argv[]) { + int ret = 0; + (void)argc; + (void)argv; + +#ifdef TEST_LWIP + #ifdef TEST_JSON_CONFIG_PATH + ret = test_lwip(TEST_JSON_CONFIG_PATH); + #else + ret = test_lwip(NULL); + #endif +#endif + if (ret < 0) + return ret; + else + return 0; +} + +#else /* !FREERTOS */ + +int main(int argc, char* argv[]) { + wolfsentry_errcode_t ret = 0; /* cppcheck-suppress unreadVariable + */ + int err = 0; + (void)argc; + (void)argv; + +#ifdef WOLFSENTRY_ERROR_STRINGS + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_REGISTER_SOURCE()); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_REGISTER_ERROR(UNIT_TEST_FAILURE, UNIT_TEST_FAILURE_MSG)); +#endif + +#ifdef TEST_INIT + ret = test_init(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_init failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_base64_decode_lenient_padding(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_base64_decode_lenient_padding failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_base64_decode_quantum(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_base64_decode_quantum failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_eventconfig_overlap_bits(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_eventconfig_overlap_bits failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_eventconfig_alignment(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_eventconfig_alignment failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_eventconfig_derogatory_threshold(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_eventconfig_derogatory_threshold failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#ifdef WOLFSENTRY_ERROR_STRINGS + ret = test_user_string_table_bounds(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_user_string_table_bounds failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif +#endif + +#ifdef TEST_LWIP + #ifdef TEST_JSON_CONFIG_PATH + ret = test_lwip(TEST_JSON_CONFIG_PATH); + #else + ret = test_lwip(NULL); + #endif + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_lwip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + +#ifdef TEST_RWLOCKS + ret = test_rw_locks(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_rw_locks_auto_downgrade(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks_auto_downgrade failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_rw_locks_reader_wakeup(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks_reader_wakeup failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_object_release_tableless(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_object_release_tableless failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_context_clone_readonly_thread(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_clone_readonly_thread failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_context_clone_fallthrough(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_clone_fallthrough failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_init_interval_conversion_failure(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_init_interval_conversion_failure failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_context_exchange_default_policy(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_exchange_default_policy failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + +#ifdef TEST_STATIC_ROUTES + ret = test_static_routes(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_lookup_af_wildcard_span_terminates(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_af_wildcard_span_terminates failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_lookup_exact_match_honors_exclude_reject(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_exact_match_honors_exclude_reject failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_lookup_action_res_filter_bits_unset(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_action_res_filter_bits_unset failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_lookup_full_scan_honors_exclude_reject(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_full_scan_honors_exclude_reject failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_insert_action_failure_detaches_route(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_action_failure_detaches_route failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + ret = test_route_insert_action_failure_releases_bitmask_af(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_action_failure_releases_bitmask_af failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + ret = test_route_insert_with_actions_marks_parent_event(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_with_actions_marks_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_insert_rejects_expired_purge_after(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_rejects_expired_purge_after failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#if !defined(WOLFSENTRY_NO_JSON) || defined(WOLFSENTRY_JSON_DUMP_UTILS) + ret = test_route_format_json_truncation_is_bounded(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_format_json_truncation_is_bounded failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_format_json_escapes_parent_event_label(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_format_json_escapes_parent_event_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_route_format_json_unknown_protocol_numeric(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_format_json_unknown_protocol_numeric failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif +#endif + +#ifdef TEST_STATIC_ROUTES + ret = test_static_routes_clone_addr_family_links(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_clone_addr_family_links failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_static_routes_id_wrap(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_id_wrap failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_static_routes_id_reclaim(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_id_reclaim failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + +#ifdef TEST_STATIC_ROUTES + ret = test_static_routes_guards(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_guards failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif - WOLFSENTRY_EXIT_ON_SYSFAILURE(closedir(corpus_dir)); - } while (0); +#ifdef TEST_DYNAMIC_RULES + ret = test_dynamic_rules(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dynamic_rules failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_inhibited_insert_actions(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_inhibited_insert_actions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_handler_deletes_own_event(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_handler_deletes_own_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_handler_error(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_handler_error failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_context_exchange_during_dispatch(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_context_exchange_during_dispatch failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_reentrant_action_delete(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_reentrant_action_delete failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_action_drop_reference_clears_results(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_action_drop_reference_clears_results failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_inhibit_actions_flag(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_inhibit_actions_flag failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_action_stop(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_action_stop failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_disabled_action(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_disabled_action failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_dont_count_hits(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_dont_count_hits failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_insert_delete_already_guard(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_insert_delete_already_guard failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dynamic_rules_regressions(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dynamic_rules_regressions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_track_peer_ephemeral_route_bound(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_track_peer_ephemeral_route_bound failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_track_peer_connect_count(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_track_peer_connect_count failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } - WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = test_event_max_length_label(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_max_length_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } - WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + ret = test_event_max_length_label_lookups(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_max_length_label_lookups failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } - WOLFSENTRY_ERROR_RERETURN(ret); -} + ret = test_event_insert_failure_leaves_id_untouched(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_insert_failure_leaves_id_untouched failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -#endif /* TEST_JSON_CORPUS */ + ret = test_event_update_config_null(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_update_config_null failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -#ifdef FREERTOS + ret = test_event_update_config_layout_with_routes(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_update_config_layout_with_routes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -int main(int argc, char* argv[]) { - int ret = 0; - (void)argc; - (void)argv; + ret = test_event_aux_event_self_reference(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_self_reference failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -#ifdef TEST_LWIP - #ifdef TEST_JSON_CONFIG_PATH - ret = test_lwip(TEST_JSON_CONFIG_PATH); - #else - ret = test_lwip(NULL); - #endif -#endif - if (ret < 0) - return ret; - else - return 0; -} + ret = test_event_aux_event_reuse_after_clear(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_reuse_after_clear failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -#else /* !FREERTOS */ + ret = test_event_aux_mark_cleared_on_owner_free(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_mark_cleared_on_owner_free failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -int main(int argc, char* argv[]) { - wolfsentry_errcode_t ret = 0; /* cppcheck-suppress unreadVariable - */ - int err = 0; - (void)argc; - (void)argv; + ret = test_event_aux_event_rejects_subevent_owner(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_rejects_subevent_owner failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -#ifdef WOLFSENTRY_ERROR_STRINGS - WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_REGISTER_SOURCE()); - WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_REGISTER_ERROR(UNIT_TEST_FAILURE, UNIT_TEST_FAILURE_MSG)); -#endif + ret = test_event_aux_event_rejects_parent_event(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_rejects_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_event_former_aux_becomes_route_parent(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_former_aux_becomes_route_parent failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } -#ifdef TEST_INIT - ret = test_init(); +#ifndef WOLFSENTRY_NO_JSON + ret = test_event_default_priority(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { - (void)fprintf(stderr, "test_init failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + (void)fprintf(stderr, "test_event_default_priority failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } -#endif -#ifdef TEST_LWIP - #ifdef TEST_JSON_CONFIG_PATH - ret = test_lwip(TEST_JSON_CONFIG_PATH); - #else - ret = test_lwip(NULL); - #endif + ret = test_json_event_max_length_label(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { - (void)fprintf(stderr, "test_lwip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + (void)fprintf(stderr, "test_json_event_max_length_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } #endif -#ifdef TEST_RWLOCKS - ret = test_rw_locks(); + ret = test_action_insert_failure_leaves_id_untouched(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { - (void)fprintf(stderr, "test_rw_locks failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + (void)fprintf(stderr, "test_action_insert_failure_leaves_id_untouched failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } -#endif -#ifdef TEST_STATIC_ROUTES - ret = test_static_routes(); + ret = test_event_aux_event_layout_pinned(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { - (void)fprintf(stderr, "test_static_routes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + (void)fprintf(stderr, "test_event_aux_event_layout_pinned failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } -#endif -#ifdef TEST_DYNAMIC_RULES - ret = test_dynamic_rules(); + ret = test_event_layout_guard_held_deleted_route(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { - (void)fprintf(stderr, "test_dynamic_rules failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + (void)fprintf(stderr, "test_event_layout_guard_held_deleted_route failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } #endif @@ -5757,6 +15185,60 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_user_values failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_reference_lifetime(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_reference_lifetime failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_kv_get2_reference_balance(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_get2_reference_balance failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_kv_get_bad_key_errcode(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_get_bad_key_errcode failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_kv_render_large_int(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_large_int failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_kv_render_float_roundtrip(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_float_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#ifndef WOLFSENTRY_C89 + ret = test_kv_render_nonfinite_float(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_nonfinite_float failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif /* !WOLFSENTRY_C89 */ + ret = test_kv_replace_keeps_value_on_id_failure(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_replace_keeps_value_on_id_failure failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_kv_readonly_overwrite_rejected(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_readonly_overwrite_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_kv_empty_key_rejected(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_empty_key_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#ifndef WOLFSENTRY_HAVE_JSON_DOM + ret = test_kv_render_string_escape(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_string_escape failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_USER_ADDR_FAMILIES @@ -5784,6 +15266,116 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json failed for " TEST_NUMERIC_JSON_CONFIG_PATH ", " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifdef WOLFSENTRY_HAVE_JSON_DOM + ret = test_cjvalue_fixes(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_cjvalue_fixes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + ret = test_json_uint64_negative(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_uint64_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_uint16_bounds(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_uint16_bounds failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_uint8_hex_interface(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_uint8_hex_interface failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_duration_negative(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_duration_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + ret = test_json_bitmask_short_address(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_bitmask_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + ret = test_json_af_local_address(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_af_local_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_failed_load_fails_closed(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_failed_load_fails_closed failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_actions_update_rejected(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_actions_update_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_prefix_bits_overbound(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_prefix_bits_overbound failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_link64_short_address(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_link64_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#ifndef WOLFSENTRY_NO_GETPROTOBY + ret = test_json_unknown_service_name(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_unknown_service_name failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_unknown_protocol_name(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_unknown_protocol_name failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + ret = test_json_tcplike_explicit_flag(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_tcplike_explicit_flag failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_link_prefix_bits_key_order(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_link_prefix_bits_key_order failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_af_local_registered_handler(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_af_local_registered_handler failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + + ret = test_json_sax_double_roundtrip(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_double_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + + ret = test_json_sax_nonfinite_dump(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_nonfinite_dump failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + + ret = test_json_sax_number_text_boundary(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_number_text_boundary failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + + ret = test_json_sax_truncated_input(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_truncated_input failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS diff --git a/wolfsentry/centijson_dom.h b/wolfsentry/centijson_dom.h index a502b47b..f02c339b 100644 --- a/wolfsentry/centijson_dom.h +++ b/wolfsentry/centijson_dom.h @@ -152,7 +152,7 @@ WOLFSENTRY_API int json_dom_parse( * callback. * * The provided writing function must write all the data provided to it - * and return zero to indicate success, or non-zero to indicate an error + * and return zero to indicate success, or a negative value to indicate an error * and abort the operation. * * Returns zero on success, JSON_ERR_OUTOFMEMORY, or an error the code returned diff --git a/wolfsentry/centijson_sax.h b/wolfsentry/centijson_sax.h index 828cf2ff..7e974e50 100644 --- a/wolfsentry/centijson_sax.h +++ b/wolfsentry/centijson_sax.h @@ -169,8 +169,8 @@ typedef struct JSON_CALLBACKS { * corresponding values in the alternating fashion (i.e. in the order * as they are in the JSON input.). * - * Application can abort the parsing operation by returning a non-zero. - * Note the non-zero return value of the callback is propagated to + * Application can abort the parsing operation by returning a negative + * value. Note the negative return value of the callback is propagated to * json_feed() and json_fini(). */ int (*process)(JSON_TYPE /*type*/, const unsigned char* /*data*/, @@ -337,7 +337,7 @@ typedef int (*JSON_DUMP_CALLBACK)(const unsigned char* /*str*/, size_t /*size*/, * handles proper escaping of control characters. * * The provided writer callback must write all the data provided to it and - * return zero to indicate success, or non-zero to indicate an error and abort + * return zero to indicate success, or a negative value to indicate an error and abort * the operation. * * All these return zero on success, JSON_ERR_OUTOFMEMORY, or an error code diff --git a/wolfsentry/centijson_value.h b/wolfsentry/centijson_value.h index 03241ca4..d1928fd8 100644 --- a/wolfsentry/centijson_value.h +++ b/wolfsentry/centijson_value.h @@ -514,8 +514,9 @@ WOLFSENTRY_API int json_value_dict_remove( /* Walking over all items in the dictionary. The callback function is called * for every item in the dictionary, providing key and value and propagating - * the user data into it. If the callback returns non-zero, the function - * aborts immediately. + * the user data into it. If the callback returns a negative value, the + * function aborts immediately and returns that value; otherwise the walk + * continues. * * Note dict_walk_ordered() is supported only if DICT_MAINTAINORDER * flag was used in init_dict(). diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 225980b6..dbb52436 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -61,15 +61,15 @@ #define WOLFSENTRY_VERSION WOLFSENTRY_VERSION_ENCODE(WOLFSENTRY_VERSION_MAJOR, WOLFSENTRY_VERSION_MINOR, WOLFSENTRY_VERSION_TINY) /*!< \brief The version recorded in wolfsentry.h, encoded as an integer @hideinitializer */ #define WOLFSENTRY_VERSION_GT(major, minor, tiny) (WOLFSENTRY_VERSION > WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is greater than that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is greater than the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_GE(major, minor, tiny) (WOLFSENTRY_VERSION >= WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is greater than or equal to that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is greater than or equal to the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_EQ(major, minor, tiny) (WOLFSENTRY_VERSION == WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version equals that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h equals the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_LT(major, minor, tiny) (WOLFSENTRY_VERSION < WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is less than that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is less than the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_LE(major, minor, tiny) (WOLFSENTRY_VERSION <= WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is less than or equal to that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is less than or equal to the given version. @hideinitializer */ /*! \brief flags to pass to wolfsentry_init_ex(), to be `OR`d together. */ typedef enum { @@ -198,7 +198,7 @@ struct wolfsentry_timecbs { */ typedef int (*sem_init_cb_t)(sem_t *sem, int pshared, unsigned int value); - /*!< Pointer to function with arguments and semantics of POSIX `sem_init()`. Currently, `pshared` and `value` are always zero as called by wolfSentry, so implementations can ignore them. */ + /*!< Pointer to function with arguments and semantics of POSIX `sem_init()`. Currently, `value` is always zero as called by wolfSentry. `pshared` is zero unless the lock is initialized with `WOLFSENTRY_LOCK_FLAG_PSHARED`, in which case it is nonzero. */ typedef int (*sem_post_cb_t)(sem_t *sem); /*!< Pointer to function with arguments and semantics of POSIX `sem_post()` */ typedef int (*sem_wait_cb_t)(sem_t *sem); @@ -709,7 +709,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_shared2mutex_is_reserved(str \param lock a pointer to the lock \param thread pointer to the `wolfsentry_thread_context` - \param flags optional `wolfsentry_lock_flags_t` + \param flags non-NULL pointer to a `wolfsentry_lock_flags_t` that receives the current flags \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. @@ -1003,7 +1003,7 @@ typedef wolfsentry_errcode_t (*wolfsentry_action_callback_t)( * @{ */ -#define WOLFSENTRY_ROUTE_DEFAULT_POLICY_MASK (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR) +#define WOLFSENTRY_ROUTE_DEFAULT_POLICY_MASK (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR | WOLFSENTRY_ACTION_RES_PORT_RESET) /*!< \brief Bit mask spanning the bits allowed by wolfsentry_route_table_default_policy_set() */ /*! \brief bit field specifying attributes of a route/rule */ @@ -1578,7 +1578,7 @@ typedef enum { \brief Clones a wolfsentry context \param clone the destination wolfsentry context, should be a pointer to a NULL pointer as this function will malloc - \param flags set to WOLFSENTRY_CLONE_FLAG_AT_CREATION to use the config at the creation of the original wolfsentry context instead of the current configuration + \param flags set to WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION to use the config at the creation of the original wolfsentry context instead of the current configuration \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. @@ -1852,7 +1852,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_from_table( \param flags flags for the route \param trigger_label a label for the trigger event (or null) \param trigger_label_len the length of the trigger_label parameter - \param action_results a pointer to results of the insert action -- all bits are cleared on entry. + \param action_results a pointer to results of the delete action -- all bits are cleared on entry. \param n_deleted a counter for the number of entries deleted \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. @@ -1877,7 +1877,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete( \param id the object ID, as returned by wolfsentry_route_insert() or wolfsentry_get_object_id() \param trigger_label a label for a trigger event (or null) \param trigger_label_len the length of the trigger_label parameter - \param action_results a pointer to results of the insert action -- all bits are cleared on entry. + \param action_results a pointer to results of the delete action -- all bits are cleared on entry. \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. @@ -2192,7 +2192,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table( /*!< \brief Variant of wolfsentry_route_event_dispatch() that accepts an explicit \p route_table. */ /*! - \brief Submit an event into wolfsentry and pass it through the filters. The action_results are cleared on entry, and can be checked to see what actions wolfsentry took, and what actions the caller should take (most saliently, #WOLFSENTRY_ACTION_RES_ACCEPT or #WOLFSENTRY_ACTION_RES_REJECT). \p action_results can be filtered with constructs like `WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)` + \brief Submit an event into wolfsentry and pass it through the filters. The action_results are cleared on entry, and can be checked to see what actions wolfsentry took, and what actions the caller should take (most saliently, #WOLFSENTRY_ACTION_RES_ACCEPT or #WOLFSENTRY_ACTION_RES_REJECT). \p action_results can be filtered with constructs like `WOLFSENTRY_MASKIN_BITS(*action_results, WOLFSENTRY_ACTION_RES_REJECT)` \param remote the remote sockaddr details \param local the local sockaddr details @@ -2275,7 +2275,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route( void *caller_arg, /* passed to action callback(s) as the caller_arg. */ wolfsentry_action_res_t *action_results ); - /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route by ID, mainly for use by application code that tracks route/session relationships. */ + /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route with the supplied route pointer, mainly for use by application code that tracks route/session relationships. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_with_inited_result( WOLFSENTRY_CONTEXT_ARGS_IN, @@ -2285,7 +2285,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit void *caller_arg, /* passed to action callback(s) as the caller_arg. */ wolfsentry_action_res_t *action_results ); - /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route by ID, and doesn't clear \p action_results on entry, mainly for use by application code that tracks route/session relationships. */ + /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route with the supplied route pointer, and doesn't clear \p action_results on entry, mainly for use by application code that tracks route/session relationships. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_max_purgeable_routes_get( WOLFSENTRY_CONTEXT_ARGS_IN, @@ -2416,7 +2416,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_get_flags( \brief Gets the metadata for a route \param route the route to get the metadata for - \param metadata a pointer to a pointer to receive the metadata + \param metadata a pointer to a caller-provided `struct wolfsentry_route_metadata_exports` to populate \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. */ @@ -2801,7 +2801,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_config( \param label_len the length of the label \param config the updated configuration - \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. + \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. Once any route has used the event as its parent, or while the event is another event's aux event or is otherwise referenced, changes to `route_private_data_size` or `route_private_data_alignment` are rejected with `INCOMPATIBLE_STATE`, even after that route is deleted, because a deleted route can still be held by a checked-out reference. \sa WOLFSENTRY_CONTEXT_ARGS_IN */ @@ -2921,6 +2921,11 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_action_insert_after( \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. + An action handler invoked during event dispatch must not call this from + within the dispatch; it returns `BUSY` while any dispatch + is in progress. Perform action-list deletions outside dispatch; + append/prepend/insert remain permitted. + \sa WOLFSENTRY_CONTEXT_ARGS_IN */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_action_delete( @@ -3075,7 +3080,7 @@ struct wolfsentry_kv_pair { #define WOLFSENTRY_KV_V_UINT(kv) ((kv)->a.v_uint) /*!< \brief Evaluates to the `uint64_t` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_UINT`. @hideinitializer */ #define WOLFSENTRY_KV_V_SINT(kv) ((kv)->a.v_sint) - /*!< \brief Evaluates to the `int64_t` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_INT`. @hideinitializer */ + /*!< \brief Evaluates to the `int64_t` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_SINT`. @hideinitializer */ #define WOLFSENTRY_KV_V_FLOAT(kv) ((kv)->a.v_float) /*!< \brief Evaluates to the `double` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_FLOAT`. @hideinitializer */ #define WOLFSENTRY_KV_V_STRING_LEN(kv) ((kv)->a.string_len) @@ -3179,7 +3184,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_sint( const char *key, int key_len, int64_t *value); - /*!< \brief Gets a `WOLFSENTRY_KV_UINT` value with the designated `key`. */ + /*!< \brief Gets a `WOLFSENTRY_KV_SINT` value with the designated `key`. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_double( WOLFSENTRY_CONTEXT_ARGS_IN, @@ -3194,7 +3199,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_float( const char *key, int key_len, double *value); - /*!< \brief Gets a `WOLFSENTRY_KV_UINT` value with the designated `key`. */ + /*!< \brief Gets a `WOLFSENTRY_KV_FLOAT` value with the designated `key`. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_string( WOLFSENTRY_CONTEXT_ARGS_IN, diff --git a/wolfsentry/wolfsentry_errcodes.h b/wolfsentry/wolfsentry_errcodes.h index 6dda150b..df262daa 100644 --- a/wolfsentry/wolfsentry_errcodes.h +++ b/wolfsentry/wolfsentry_errcodes.h @@ -260,7 +260,7 @@ static inline int WOLFSENTRY_ERROR_DECODE_LINE_NUMBER(wolfsentry_errcode_t x) { /*!< \brief Get a shared lock on the current context, and on failure, return the `wolfsentry_errcode_t`. @hideinitializer */ #define WOLFSENTRY_PROMOTABLE_EX(ctx) wolfsentry_context_lock_shared_with_reservation_abstimed(ctx, thread, NULL) - /*!< \brief Get a mutex on a `wolfsentry_context`, evaluating to the resulting `wolfsentry_errcode_t`. @hideinitializer */ + /*!< \brief Get a shared lock with mutex promotion reservation on a `wolfsentry_context`, evaluating to the resulting `wolfsentry_errcode_t`. @hideinitializer */ #define WOLFSENTRY_PROMOTABLE_OR_RETURN() do { \ wolfsentry_errcode_t _lock_ret; \ @@ -304,7 +304,7 @@ static inline int WOLFSENTRY_ERROR_DECODE_LINE_NUMBER(wolfsentry_errcode_t x) { /*!< \brief Calculate the `wolfsentry_errcode_t` return value for an expression `y`, then unlock the current context, and finally, return the encoded `wolfsentry_errcode_t`. @hideinitializer */ #define WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN(name) do { WOLFSENTRY_UNLOCK_FOR_RETURN(); WOLFSENTRY_SUCCESS_RETURN(name); } while (0) - /*!< \brief Unlock the current context, and return a `wolfsentry_errcode_t` encoding the current source ID and line number, and the designated short-form success `name` (e.g. `INVALID_ARG`). @hideinitializer */ + /*!< \brief Unlock the current context, and return a `wolfsentry_errcode_t` encoding the current source ID and line number, and the designated short-form success `name` (e.g. `OK`). @hideinitializer */ #define WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN_RECODED(x) do { WOLFSENTRY_UNLOCK_FOR_RETURN(); WOLFSENTRY_SUCCESS_RETURN_RECODED(x); } while (0) /*!< \brief Unlock the current context, then take an encoded `wolfsentry_errcode_t` `x`, recode it with the current source ID and line number, and return it. @hideinitializer */ #define WOLFSENTRY_SUCCESS_UNLOCK_AND_RERETURN(x) do { WOLFSENTRY_UNLOCK_FOR_RETURN(); WOLFSENTRY_SUCCESS_RERETURN(x); } while (0) diff --git a/wolfsentry/wolfsentry_lwip.h b/wolfsentry/wolfsentry_lwip.h index 2a459f94..202d7c4f 100644 --- a/wolfsentry/wolfsentry_lwip.h +++ b/wolfsentry/wolfsentry_lwip.h @@ -39,6 +39,8 @@ * `FILT_PORT_UNREACHABLE` -- Call into wolfSentry when inbound traffic attempts to reach an unlistened/unbound port
* `FILT_INBOUND_ERR` -- Call into wolfSentry when inbound traffic results in detection of an error by lwIP
* `FILT_OUTBOUND_ERR` -- Call into wolfSentry when outbound traffic results in detection of an error by lwIP
+ * + * lwIP has a single process-wide set of filter slots, so at most one wolfSentry context can own them at a time. The first context to install a callback with a nonzero mask becomes the owner of all slots. While it owns them, any installation or disabling call from a different context fails with `WOLFSENTRY_ERROR_ID_INCOMPATIBLE_STATE` and leaves the owner's callbacks in place. Ownership is released when the owner disables every slot it installed (mask zero), or when the owner is shut down, which runs `wolfsentry_cleanup_lwip_filter_callbacks()`; a replacement context can install only after that. A filter callback denies the packet whenever the wolfSentry dispatch fails or no context is attached. */ #ifndef WOLFSENTRY_LWIP_H diff --git a/wolfsentry/wolfsentry_settings.h b/wolfsentry/wolfsentry_settings.h index 6670084c..5864a0f0 100644 --- a/wolfsentry/wolfsentry_settings.h +++ b/wolfsentry/wolfsentry_settings.h @@ -255,7 +255,7 @@ #undef WOLFSENTRY_NO_ERROR_STRINGS #define WOLFSENTRY_NO_PROTOCOL_NAMES - /*!< \brief If defined, omit APIs for rendering error codes and source code files in human readable form. They will be rendered numerically. */ + /*!< \brief If defined, omit APIs for converting between address family names and numbers. Address families will be rendered numerically. Protocol and service name lookup is controlled separately by `WOLFSENTRY_NO_GETPROTOBY`. */ #undef WOLFSENTRY_NO_PROTOCOL_NAMES #define WOLFSENTRY_NO_ADDR_BITMASK_MATCHING diff --git a/wolfsentry/wolfssl_test.h b/wolfsentry/wolfssl_test.h index 6163b935..a3c2a51b 100644 --- a/wolfsentry/wolfssl_test.h +++ b/wolfsentry/wolfssl_test.h @@ -373,7 +373,7 @@ static int wolfsentry_setup( WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD, - 0 /* event_label_len */, 0 /* event_label */, &id, + NULL /* event_label */, 0 /* event_label_len */, &id, &action_results)) < 0) { fprintf(stderr, "wolfsentry_route_insert() returned " WOLFSENTRY_ERROR_FMT "\n", @@ -432,7 +432,7 @@ static int wolfsentry_setup( WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD, - 0 /* event_label_len */, 0 /* event_label */, &id, + NULL /* event_label */, 0 /* event_label_len */, &id, &action_results)) < 0) { fprintf(stderr, "wolfsentry_route_insert() returned " WOLFSENTRY_ERROR_FMT "\n",