3 Commits

Author SHA1 Message Date
lns
9678d33d0c sync'd with ndpiSimpleIntegration from https://github.com/ntop/nDPI.git
Signed-off-by: lns <matzeton@googlemail.com>
2023-06-01 19:33:51 +02:00
Toni Uhlig
b94bf63067 sync'd with ndpiSimpleIntegration from https://github.com/ntop/nDPI.git
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2021-07-04 20:02:48 +02:00
Toni Uhlig
0c24bb5d04 proposal ready for review
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
2020-06-24 22:04:13 +02:00
2651 changed files with 1211 additions and 271232 deletions

View File

@@ -1,19 +0,0 @@
version: 2.1
jobs:
build:
docker:
- image: ubuntu:latest
steps:
- checkout
- run: export DEBIAN_FRONTEND=noninteractive
- run: apt-get update -qq
- run: |
env DEBIAN_FRONTEND=noninteractive \
apt-get install -y -qq \
coreutils wget git unzip make cmake binutils gcc g++ autoconf automake flex bison texinfo \
libtool pkg-config gettext libjson-c-dev flex bison libpcap-dev zlib1g-dev
- run: |
cmake -S . -B build -DENABLE_SYSTEMD=ON -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON
- run: |
cmake --build build --verbose

View File

@@ -51,7 +51,7 @@ Cpp11BracedListStyle: true
IndentWidth: 4
TabWidth: 4
UseTab: Never
BreakBeforeBraces: Allman
BreakBeforeBraces: Linux
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpacesInAngles : false

View File

@@ -1,38 +0,0 @@
name: ArchLinux PKGBUILD
on:
push:
branches:
- main
- tmp
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
env:
CMAKE_C_FLAGS: -Werror
steps:
- uses: actions/checkout@v3
with:
submodules: false
fetch-depth: 1
- name: Prepare for ArchLinux packaging
run: |
sudo chmod -R 0777 .
mv -v packages/archlinux packages/ndpid-testing
- uses: 2m/arch-pkgbuild-builder@v1.16
with:
debug: true
target: 'pkgbuild'
pkgname: 'packages/ndpid-testing'
- name: Upload PKG
uses: actions/upload-artifact@v3
with:
name: nDPId-archlinux-packages
path: packages/ndpid-testing/*.pkg.tar.zst

View File

@@ -1,59 +0,0 @@
name: CentOs
on:
push:
branches:
- main
- tmp
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
release:
types: [created]
jobs:
centos8:
runs-on: ubuntu-latest
container: 'centos:8'
steps:
- uses: actions/checkout@v3
with:
submodules: false
fetch-depth: 1
- name: Install CentOs Prerequisites
run: |
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
yum -y update
yum -y install curl gpg
curl 'https://packages.ntop.org/centos/ntop.repo' > /etc/yum.repos.d/ntop.repo
curl 'https://packages.ntop.org/centos/RPM-GPG-KEY-deri' | gpg --import
yum -y install yum-utils dnf-plugins-core epel-release
dnf config-manager --set-enabled powertools
yum -y update
yum -y install rpm-build gcc gcc-c++ autoconf automake make cmake flex bison gettext pkg-config libtool ndpi-dev libpcap-devel zlib-devel python3.8 git wget unzip /usr/lib64/libasan.so.5.0.0 /usr/lib64/libubsan.so.1.0.0
repoquery -l ndpi-dev
- name: Configure nDPId
run: |
mkdir build && cd build
cmake .. -DENABLE_SYSTEMD=ON -DBUILD_EXAMPLES=ON -DENABLE_SANITIZER=ON -DNDPI_NO_PKGCONFIG=ON -DSTATIC_LIBNDPI_INSTALLDIR=/usr
- name: Build nDPId
run: |
make -C build all VERBOSE=1
- name: CPack RPM
run: |
cd ./build && cpack -G RPM && cd ..
- name: Upload RPM
uses: actions/upload-artifact@v3
with:
name: nDPId-centos-packages
path: build/*.rpm
- name: Upload on Failure
uses: actions/upload-artifact@v3
if: failure()
with:
name: autoconf-config-log
path: |
build/CMakeCache.txt
libnDPI/config.log

View File

@@ -1,25 +0,0 @@
name: Docker Build
on:
push:
branches:
- 'main'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: utoni/ndpid:latest

View File

@@ -1,66 +0,0 @@
name: OpenWrt Build
on:
push:
branches:
- main
- tmp
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
release:
types: [created]
jobs:
build:
name: ${{ matrix.arch }} ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- arch: arm_cortex-a9_vfpv3-d16
target: mvebu-cortexa9
- arch: mips_24kc
target: ath79-generic
- arch: mipsel_24kc
target: mt7621
- arch: powerpc_464fp
target: apm821xx-nand
- arch: aarch64_cortex-a53
target: mvebu-cortexa53
- arch: arm_cortex-a15_neon-vfpv4
target: armvirt-32
- arch: i386_pentium-mmx
target: x86-geode
- arch: x86_64
target: x86-64
steps:
- uses: actions/checkout@v3
with:
submodules: false
fetch-depth: 1
- name: Build
uses: openwrt/gh-action-sdk@main
env:
ARCH: ${{ matrix.arch }}-snapshot
FEED_DIR: ${{ github.workspace }}/packages/openwrt
FEEDNAME: ndpid_openwrt_packages_ci
PACKAGES: nDPId-testing
V: s
- name: Store packages
uses: actions/upload-artifact@v3
with:
name: nDPId-${{ matrix.arch}}-${{ matrix.target }}
path: bin/packages/${{ matrix.arch }}/ndpid_openwrt_packages_ci/*.ipk

View File

@@ -1,262 +0,0 @@
name: Build
on:
push:
branches:
- main
- tmp
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
release:
types: [created]
jobs:
test:
name: ${{ matrix.os }} ${{ matrix.compiler }}
runs-on: ${{ matrix.os }}
env:
CMAKE_C_COMPILER: ${{ matrix.compiler }}
CMAKE_C_FLAGS: -Werror ${{ matrix.cflags }}
CMAKE_C_EXE_LINKER_FLAGS: ${{ matrix.ldflags }}
CMAKE_MODULE_LINKER_FLAGS: ${{ matrix.ldflags }}
strategy:
fail-fast: true
matrix:
include:
- compiler: "gcc"
os: "ubuntu-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=ON"
sanitizer: "-DENABLE_SANITIZER=OFF -DENABLE_SANITIZER_THREAD=OFF"
coverage: "-DENABLE_COVERAGE=OFF"
poll: "-DFORCE_POLL=OFF"
upload: true
upload_suffix: ""
ndpi_min_version: "4.8"
- compiler: "gcc"
os: "ubuntu-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=ON"
ndpid_zlib: "-DENABLE_ZLIB=ON"
sanitizer: "-DENABLE_SANITIZER=OFF -DENABLE_SANITIZER_THREAD=OFF"
coverage: "-DENABLE_COVERAGE=OFF"
poll: "-DFORCE_POLL=OFF"
upload: true
upload_suffix: "-host-gcrypt"
ndpi_min_version: "4.8"
- compiler: "clang"
os: "ubuntu-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=OFF"
sanitizer: "-DENABLE_SANITIZER=OFF -DENABLE_SANITIZER_THREAD=OFF"
coverage: "-DENABLE_COVERAGE=OFF"
poll: "-DFORCE_POLL=OFF"
upload: true
upload_suffix: "-no-zlib"
ndpi_min_version: "4.8"
- compiler: "gcc"
os: "ubuntu-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=ON"
sanitizer: "-DENABLE_SANITIZER=ON"
coverage: "-DENABLE_COVERAGE=ON"
poll: "-DFORCE_POLL=ON"
upload: false
ndpi_min_version: "4.8"
- compiler: "clang"
os: "ubuntu-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=ON"
sanitizer: "-DENABLE_SANITIZER=ON"
coverage: "-DENABLE_COVERAGE=OFF"
poll: "-DFORCE_POLL=OFF"
upload: false
ndpi_min_version: "4.8"
- compiler: "clang-12"
os: "ubuntu-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=ON"
sanitizer: "-DENABLE_SANITIZER_THREAD=ON"
coverage: "-DENABLE_COVERAGE=OFF"
poll:
upload: false
ndpi_min_version: "4.8"
- compiler: "gcc-10"
os: "ubuntu-20.04"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=OFF"
sanitizer: "-DENABLE_SANITIZER=ON"
coverage: "-DENABLE_COVERAGE=OFF"
poll: "-DFORCE_POLL=ON"
upload: false
ndpi_min_version: "4.8"
- compiler: "gcc-7"
os: "ubuntu-20.04"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=ON"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=ON"
sanitizer: "-DENABLE_SANITIZER=ON"
coverage: "-DENABLE_COVERAGE=OFF"
poll: "-DFORCE_POLL=OFF"
upload: false
ndpi_min_version: "4.8"
- compiler: "clang"
os: "macOS-latest"
ndpi_build: "-DBUILD_NDPI=ON"
ndpid_examples: "-DBUILD_EXAMPLES=OFF"
ndpid_gcrypt: "-DNDPI_WITH_GCRYPT=OFF"
ndpid_zlib: "-DENABLE_ZLIB=ON"
examples: "-DBUILD_EXAMPLES=OFF"
sanitizer: "-DENABLE_SANITIZER=OFF"
coverage: "-DENABLE_COVERAGE=OFF"
poll:
upload: false
ndpi_min_version: "4.8"
steps:
- name: Print Matrix
run: |
echo '----------------------------------------'
echo '| OS.......: ${{ matrix.os }}'
echo '| CC.......: ${{ matrix.compiler }}'
echo "| CFLAGS...: $CMAKE_C_FLAGS"
echo "| LDFLAGS..: $CMAKE_C_EXE_LINKER_FLAGS"
echo '|---------------------------------------'
echo '| nDPI min.: ${{ matrix.ndpi_min_version }}'
echo '| GCRYPT...: ${{ matrix.ndpid_gcrypt }}'
echo '| ZLIB.....: ${{ matrix.ndpid_zlib }}'
echo '| ForcePoll: ${{ matrix.poll }}'
echo '|---------------------------------------'
echo '| SANITIZER: ${{ matrix.sanitizer }}'
echo '| COVERAGE.: ${{ matrix.coverage }}'
echo '|---------------------------------------'
echo '| UPLOAD...: ${{ matrix.upload }}'
echo '----------------------------------------'
- uses: actions/checkout@v3
with:
submodules: false
fetch-depth: 1
- name: Install MacOS Prerequisites
if: startsWith(matrix.os, 'macOS')
run: |
brew install coreutils flock automake make unzip cmake pkg-config git wget
#wget 'https://www.tcpdump.org/release/libpcap-1.10.4.tar.gz'
#tar -xzvf libpcap-1.10.4.tar.gz
#cd libpcap-1.10.4
#./configure && make install
#cd ..
wget 'https://github.com/ntop/nDPI/archive/refs/heads/dev.zip' -O libndpi-dev.zip
unzip libndpi-dev.zip
cd nDPI-dev
./autogen.sh --prefix=/usr/local --with-only-libndpi && make install
- name: Install Ubuntu Prerequisites
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install autoconf automake cmake libtool pkg-config gettext libjson-c-dev flex bison libpcap-dev zlib1g-dev
sudo apt-get install ${{ matrix.compiler }} lcov iproute2
- name: Install Ubuntu Prerequisites (libgcrypt)
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.ndpid_gcrypt, '-DNDPI_WITH_GCRYPT=ON')
run: |
sudo apt-get install libgcrypt20-dev
- name: Install Ubuntu Prerequisities (zlib)
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.ndpid_zlib, '-DENABLE_ZLIB=ON')
run: |
sudo apt-get install zlib1g-dev
- name: Checking Network Buffer Size
run: |
C_VAL=$(cat config.h | sed -n 's/^#define\s\+NETWORK_BUFFER_MAX_SIZE\s\+\([0-9]\+\).*$/\1/gp')
PY_VAL=$(cat dependencies/nDPIsrvd.py | sed -n 's/^NETWORK_BUFFER_MAX_SIZE = \([0-9]\+\).*$/\1/gp')
test ${C_VAL} = ${PY_VAL}
- name: Configure nDPId
run: |
cmake -S . -B build -DCMAKE_C_COMPILER="$CMAKE_C_COMPILER" -DCMAKE_C_FLAGS="$CMAKE_C_FLAGS" -DCMAKE_MODULE_LINKER_FLAGS="$CMAKE_MODULE_LINKER_FLAGS" -DCMAKE_C_EXE_LINKER_FLAGS="$CMAKE_C_EXE_LINKER_FLAGS" \
-DENABLE_SYSTEMD=ON \
${{ matrix.poll }} ${{ matrix.coverage }} ${{ matrix.sanitizer }} ${{ matrix.ndpi_build }} \
${{ matrix.ndpid_examples }} ${{ matrix.ndpid_zlib }} ${{ matrix.ndpid_gcrypt }}
- name: Build nDPId
run: |
cmake --build build --verbose
- name: Build single nDPId executable (invoke CC directly)
if: endsWith(matrix.compiler, 'gcc-7') == false && startsWith(matrix.coverage, '-DENABLE_COVERAGE=OFF') && startsWith(matrix.sanitizer, '-DENABLE_SANITIZER=ON') && startsWith(matrix.ndpid_gcrypt, '-DNDPI_WITH_GCRYPT=OFF') && startsWith(matrix.ndpid_zlib, '-DENABLE_ZLIB=ON')
run: |
cc -Wall -Wextra -std=gnu99 ${{ matrix.poll }} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=leak nDPId.c nio.c utils.c -I./build/libnDPI/include/ndpi -I. -I./dependencies -I./dependencies/jsmn -I./dependencies/uthash/include -o /tmp/a.out -lpcap ./build/libnDPI/lib/libndpi.a -pthread -lm -lz
- name: Test EXEC
run: |
./build/nDPId-test
./build/nDPId -h || test $? -eq 1
./build/nDPIsrvd -h || test $? -eq 1
- name: Test DIFF
if: startsWith(matrix.os, 'macOS') == false && startsWith(matrix.ndpid_gcrypt, '-DNDPI_WITH_GCRYPT=OFF')
run: |
./test/run_tests.sh ./libnDPI ./build/nDPId-test
- name: Daemon
if: endsWith(matrix.compiler, 'gcc') || endsWith(matrix.compiler, 'clang')
run: |
make -C ./build daemon VERBOSE=1
make -C ./build daemon VERBOSE=1
- name: Coverage
if: startsWith(matrix.coverage, '-DENABLE_COVERAGE=ON')
run: |
make -C ./build coverage
- name: Dist
if: startsWith(matrix.os, 'macOS') == false && matrix.upload == false
run: |
make -C ./build dist
- name: CPack DEB
if: startsWith(matrix.os, 'macOS') == false
run: |
cd ./build && cpack -G DEB && sudo dpkg -i nDPId-*.deb && cd ..
- name: Upload DEB
if: startsWith(matrix.os, 'macOS') == false && matrix.upload
uses: actions/upload-artifact@v3
with:
name: nDPId-debian-packages_${{ matrix.compiler }}${{ matrix.upload_suffix }}
path: build/*.deb
- name: Test systemd
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.compiler, 'gcc')
run: |
ip -c address
sudo systemctl daemon-reload
sudo systemctl enable ndpid@lo
sudo systemctl start ndpid@lo
SYSTEMCTL_RET=3; while (( $SYSTEMCTL_RET == 3 )); do systemctl is-active ndpid@lo.service; SYSTEMCTL_RET=$?; sleep 1; done
sudo systemctl status ndpisrvd.service ndpid@lo.service || true
sudo systemctl show ndpisrvd.service ndpid@lo.service -p SubState,ActiveState || true
journalctl --no-tail --no-pager -u ndpisrvd.service -u ndpid@lo.service
- name: Build against libnDPI-${{ matrix.ndpi_min_version }}
if: matrix.upload == false && startsWith(matrix.os, 'ubuntu')
run: |
mkdir build-local-ndpi && cd build-local-ndpi
WGET_RET=0
wget 'https://github.com/ntop/nDPI/archive/refs/tags/${{ matrix.ndpi_min_version }}.tar.gz' || { WGET_RET=$?; true; }
echo "wget returned: ${WGET_RET}"
test $WGET_RET -ne 8 || echo "::warning file=nDPId.c::New libnDPI release required to build against release tarball."
test $WGET_RET -ne 0 || { tar -xzvf ${{ matrix.ndpi_min_version }}.tar.gz && \
cd nDPI-${{ matrix.ndpi_min_version }} && \
./autogen.sh --prefix=/usr --with-only-libndpi CC="${{ matrix.compiler }}" CXX=false \
CFLAGS="$CMAKE_C_FLAGS" && make && sudo make install; cd ..; }
test $WGET_RET -ne 0 || { echo "::info file=CMakeLists.txt::Running CMake.."; \
cmake -S .. -DCMAKE_C_COMPILER="$CMAKE_C_COMPILER" -DCMAKE_C_FLAGS="$CMAKE_C_FLAGS" \
-DCMAKE_C_EXE_LINKER_FLAGS="$CMAKE_C_EXE_LINKER_FLAGS" \
-DBUILD_NDPI=OFF -DENABLE_SANITIZER=OFF \
${{ matrix.poll }} ${{ matrix.coverage }} \
${{ matrix.ndpid_examples }}; }
test $WGET_RET -ne 0 || { echo "::info file=CMakeLists.txt:Running Make.."; cmake --build . --verbose; }
test $WGET_RET -eq 0 -o $WGET_RET -eq 8

View File

@@ -1,61 +0,0 @@
on:
push:
branches:
- main
- tmp
pull_request:
types: [opened, synchronize, reopened]
name: Sonarcloud Scan
jobs:
sonarcloud:
runs-on: ubuntu-latest
env:
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
# - uses: actions/checkout@v3
# - name: Set up Python 3.8 for gcovr
# uses: actions/setup-python@v4
# with:
# python-version: 3.8
# - name: install gcovr 5.0
# run: |
# pip install gcovr==5.0 # 5.1 is not supported
- name: Install sonar-scanner and build-wrapper
uses: SonarSource/sonarcloud-github-c-cpp@v2
- name: Install Prerequisites
run: |
sudo apt-get update
sudo apt-get install autoconf automake cmake libtool pkg-config gettext libjson-c-dev flex bison libpcap-dev zlib1g-dev
- name: Run build-wrapper
run: |
mkdir build
cmake -S . -B build -DBUILD_NDPI=ON -DDENABLE_ZLIB=ON -DNDPI_WITH_GCRYPT=OFF
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Release
# - name: Run tests
# run: |
# for file in $(ls libnDPI/tests/cfgs/*/pcap/*.pcap libnDPI/tests/cfgs/*/pcap/*.pcapng libnDPI/tests/cfgs/*/pcap/*.cap); do \
# echo -n "${file} "; \
# ./build/nDPId-test "${file}" >/dev/null 2>/dev/null; \
# echo "[ok]"; \
# done
# - name: Collect coverage into one XML report
# run: |
# gcovr --sonarqube > coverage.xml
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner \
--define sonar.branch.name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} \
--define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \
--define sonar.organization=lnslbrty \
--define sonar.projectKey=lnslbrty_nDPId \
--define sonar.exclusions=dependencies/uthash/tests/** \
--define sonar.verbose=true \
--define sonar.python.version=3.8 \
--define sonar.cfamily.gcov.reportsPath=coverage.xml

12
.gitignore vendored
View File

@@ -1,12 +0,0 @@
# python related
*.pyc
__pycache__
# go related
*.sum
# lockfiles generated by some shell scripts
*.lock
# building folder
build

View File

@@ -1,131 +0,0 @@
image: debian:stable
stages:
- build_and_test
before_script:
- export DEBIAN_FRONTEND=noninteractive
- apt-get update -qq
- >
apt-get install -y -qq \
coreutils sudo \
build-essential make cmake binutils gcc clang autoconf automake \
libtool pkg-config git wget unzip \
libpcap-dev libgpg-error-dev libjson-c-dev zlib1g-dev \
netcat-openbsd python3 python3-jsonschema tree lcov iproute2
after_script:
- test -r /tmp/nDPIsrvd.log && cat /tmp/nDPIsrvd.log
- test -r /tmp/nDPId.log && cat /tmp/nDPId.log
build_and_test_static_libndpi_tsan:
script:
# test for NETWORK_BUFFER_MAX_SIZE C and Python value equality
- C_VAL=$(cat config.h | sed -n 's/^#define\s\+NETWORK_BUFFER_MAX_SIZE\s\+\([0-9]\+\).*$/\1/gp')
- PY_VAL=$(cat dependencies/nDPIsrvd.py | sed -n 's/^NETWORK_BUFFER_MAX_SIZE = \([0-9]\+\).*$/\1/gp')
- test ${C_VAL} = ${PY_VAL}
# test for nDPId_PACKETS_PLEN_MAX C and Python value equality
- C_VAL=$(cat config.h | sed -n 's/^#define\s\+nDPId_PACKETS_PLEN_MAX\s\+\([0-9]\+\).*$/\1/gp')
- PY_VAL=$(cat dependencies/nDPIsrvd.py | sed -n 's/^nDPId_PACKETS_PLEN_MAX = \([0-9]\+\).*$/\1/gp')
- test ${C_VAL} = ${PY_VAL}
# static linked build
- mkdir build-clang-tsan
- cd build-clang-tsan
- env CMAKE_C_FLAGS='-Werror' CMAKE_C_COMPILER='clang' cmake .. -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON -DBUILD_NDPI_FORCE_GIT_UPDATE=ON -DENABLE_SANITIZER_THREAD=ON -DENABLE_ZLIB=ON
- make clean-libnDPI
- make libnDPI
- tree libnDPI
- make install VERBOSE=1 DESTDIR="$(realpath ../_install)"
- cd ..
- ./_install/usr/local/bin/nDPId-test
- ./test/run_tests.sh ./libnDPI ./_install/usr/local/bin/nDPId-test
artifacts:
expire_in: 1 week
paths:
- _install/
stage: build_and_test
build_and_test_static_libndpi:
script:
- mkdir build-cmake-submodule
- cd build-cmake-submodule
- env CMAKE_C_FLAGS='-Werror' cmake .. -DENABLE_SYSTEMD=ON -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON -DBUILD_NDPI_FORCE_GIT_UPDATE=ON -DENABLE_ZLIB=ON
- make clean-libnDPI
- make libnDPI
- tree libnDPI
- make install VERBOSE=1 DESTDIR="$(realpath ../_install)"
- cpack -G DEB
- sudo dpkg -i nDPId-*.deb
- cd ..
- test -x /bin/systemctl && sudo systemctl daemon-reload
- test -x /bin/systemctl && sudo systemctl enable ndpid@lo
- test -x /bin/systemctl && sudo systemctl start ndpid@lo
- test -x /bin/systemctl && sudo systemctl status ndpisrvd.service ndpid@lo.service
- test -x /bin/systemctl && sudo systemctl stop ndpid@lo
- ./build-cmake-submodule/nDPId-test
- ./test/run_tests.sh ./libnDPI ./build-cmake-submodule/nDPId-test
- >
if ldd ./build-cmake-submodule/nDPId | grep -qoEi libndpi; then \
echo 'nDPId linked against a static libnDPI should not contain a shared linked libnDPI.' >&2; false; fi
- cc -Wall -Wextra -std=gnu99 nDPId.c nio.c utils.c -I./build-cmake-submodule/libnDPI/include/ndpi -I. -I./dependencies -I./dependencies/jsmn -I./dependencies/uthash/include -o /tmp/a.out -lpcap ./build-cmake-submodule/libnDPI/lib/libndpi.a -pthread -lm -lz
artifacts:
expire_in: 1 week
paths:
- build-cmake-submodule/*.deb
- _install/
stage: build_and_test
build_and_test_static_libndpi_coverage:
script:
- mkdir build-cmake-submodule
- cd build-cmake-submodule
- env CMAKE_C_FLAGS='-Werror' cmake .. -DENABLE_SYSTEMD=ON -DENABLE_COVERAGE=ON -DBUILD_EXAMPLES=ON -DBUILD_NDPI=ON -DBUILD_NDPI_FORCE_GIT_UPDATE=ON -DENABLE_SANITIZER=ON -DENABLE_ZLIB=ON
- make clean-libnDPI
- make libnDPI
- tree libnDPI
- make install VERBOSE=1 DESTDIR="$(realpath ../_install)"
- cd ..
- ./build-cmake-submodule/nDPId-test
- ./test/run_tests.sh ./libnDPI ./build-cmake-submodule/nDPId-test
# generate coverage report
- make -C ./build-cmake-submodule coverage || true
- >
if ldd build/nDPId | grep -qoEi libndpi; then \
echo 'nDPId linked against a static libnDPI should not contain a shared linked libnDPI.' >&2; false; fi
artifacts:
expire_in: 1 week
paths:
- build-cmake-submodule/coverage_report
- _install/
stage: build_and_test
build_dynamic_libndpi:
script:
# pkg-config dynamic linked build
- git clone https://github.com/ntop/nDPI.git
- cd nDPI
- ./autogen.sh --prefix="$(realpath ../_install)" --enable-option-checking=fatal
- make install V=s
- cd ..
- tree ./_install
- mkdir build
- cd build
- export CMAKE_PREFIX_PATH="$(realpath ../_install)"
- env CMAKE_C_FLAGS='-Werror' cmake .. -DBUILD_EXAMPLES=ON -DENABLE_SANITIZER=ON -DENABLE_MEMORY_PROFILING=ON -DENABLE_ZLIB=ON
- make all VERBOSE=1
- make install VERBOSE=1 DESTDIR="$(realpath ../_install)"
- cd ..
- tree ./_install
- ./build/nDPId-test
- ./build/nDPId -h || test $? -eq 1
- ./build/nDPIsrvd -h || test $? -eq 1
# dameon start/stop test
- NUSER=nobody make -C ./build daemon VERBOSE=1
- NUSER=nobody make -C ./build daemon VERBOSE=1
# make dist
- make -C ./build dist
artifacts:
expire_in: 1 week
paths:
- _install/
stage: build_and_test

14
.gitmodules vendored
View File

@@ -1,14 +0,0 @@
[submodule "libnDPI"]
path = libnDPI
url = https://github.com/ntop/nDPI
branch = dev
update = rebase
[submodule "examples/js-rt-analyzer"]
path = examples/js-rt-analyzer
url = https://gitlab.com/verzulli/ndpid-rt-analyzer.git
[submodule "examples/js-rt-analyzer-frontend"]
path = examples/js-rt-analyzer-frontend
url = https://gitlab.com/verzulli/ndpid-rt-analyzer-frontend.git
[submodule "examples/cxx-graph"]
path = examples/cxx-graph
url = https://github.com/utoni/nDPId-Graph.git

View File

@@ -1,64 +0,0 @@
# CHANGELOG
#### nDPId 1.6 (Nov 2023)
- Added Event I/O abstraction layer (supporting only poll/epoll by now)
- Support for OSX and *BSD systems
- Added proper DLT_RAW dissection for IPv4 and IPv6
- Improved TCP timeout handling if FIN/RST seen which caused Midstream TCP flows when there shouldn't be any
- Fixed a crash if `nDPId -o value=''` was used
- Added OpenWrt packaging
- Added new flow event "analyse" used to give some statistical information about active flows
- Added new analyse event daemon which generates CSV files from such events
- Fixed a crash in nDPIsrvd if a collector closes a connection
- Support `nDPId` to send it's data to a UDP endpoint instead of a nDPIsrvd collector
- Added events and flow states documentation
- Added basic systemd support
- Fixed a bug in base64 encoding which could lead to invalid base64 strings
- Added some machine learning examples
- Fixed various smaller bugs
- Fixed nDPIsrvd bug which causes invalid JSON messages sent to Distributors
#### nDPId 1.5 (Apr 2022)
- Improved nDPId cross compilation
- zLib flow memory compression (Experimental!)
- Memory profiling for nDPId-test
- JSMN with parent link support for subtoken iteration
- Refactored nDPIsrvd buffer and buffer bloat handling
- Upgraded JSMN/uthash
- Improved nDPIsrvd.(h|py) debugging capability for client apps
- Advanced flow usage logging usable for memory profiling
- Support for dissection additional layer2/layer3 protocols
- Serialize more JSON information
- Add TCP/IP support for nDPIsrvd
- Improved nDPIsrvd connection lost behaviour
- Reworked Python/C distributor API
- Support read()/recv() timeouts and nonblocking I/O
#### nDPId 1.4 (Jun 2021)
- Use layer4 specific flow timeouts for nDPId
- Reworked layer4 flow length names and calculations (use only layer4 payload w/o any previous headers) for nDPId
- Build system cleanup and cosmetics
#### nDPId 1.3 (May 2021)
- Added missing datalink layer types
#### nDPId 1.2 (May 2021)
- OpenWrt compatible build system
#### nDPId 1.1 (May 2021)
- Added License information
#### nDPId 1.0 (May 2021)
- First public release

View File

@@ -1,509 +0,0 @@
cmake_minimum_required(VERSION 3.12.4)
project(nDPId C)
if(CMAKE_COMPILER_IS_GNUCXX)
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)
message(STATUS "${CMAKE_C_COMPILER} supports C11 standard.")
else ()
message(FATAL_ERROR "C Compiler with C11 standard needed. Therefore a gcc compiler with a version equal or higher than 4.7 is needed.")
endif()
endif(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -D_DEFAULT_SOURCE=1 -D_GNU_SOURCE=1")
if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
message(FATAL_ERROR "In-source builds are not allowed.\n"
"Please remove ${PROJECT_SOURCE_DIR}/CMakeCache.txt\n"
"and\n"
"${PROJECT_SOURCE_DIR}/CMakeFiles\n"
"Create a build directory somewhere and run CMake again.")
endif()
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
find_package(PkgConfig REQUIRED)
set(CPACK_PACKAGE_CONTACT "toni@impl.cc")
set(CPACK_DEBIAN_PACKAGE_NAME "nDPId")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON)
set(CPACK_STRIP_FILES ON)
set(CPACK_PACKAGE_VERSION_MAJOR 1)
set(CPACK_PACKAGE_VERSION_MINOR 6)
set(CPACK_PACKAGE_VERSION_PATCH 0)
# Note: CPACK_PACKAGING_INSTALL_PREFIX and CMAKE_INSTALL_PREFIX are *not* the same.
# It is used only to ease environment file loading via systemd.
set(CPACK_PACKAGING_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
include(CPack)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(CheckEpoll)
check_epoll(HAS_EPOLL)
if(HAS_EPOLL)
option(FORCE_POLL "Force the use of poll() instead of epoll()." OFF)
if(NOT FORCE_POLL)
set(EPOLL_DEFS "-DENABLE_EPOLL=1")
endif()
else()
if(BUILD_EXAMPLES)
message(FATAL_ERROR "Examples are using epoll event I/O. Without epoll available, you can not build/run those.")
endif()
endif()
if(NOT MATH_FUNCTION_EXISTS AND NOT NEED_LINKING_AGAINST_LIBM)
CHECK_FUNCTION_EXISTS(log2f MATH_FUNCTION_EXISTS)
if(NOT MATH_FUNCTION_EXISTS)
unset(MATH_FUNCTION_EXISTS CACHE)
list(APPEND CMAKE_REQUIRED_LIBRARIES m)
CHECK_FUNCTION_EXISTS(log2f MATH_FUNCTION_EXISTS)
if(MATH_FUNCTION_EXISTS)
set(NEED_LINKING_AGAINST_LIBM TRUE CACHE BOOL "" FORCE)
else()
check_library_exists(m sqrt "" NEED_LINKING_AGAINST_LIBM)
if(NOT NEED_LINKING_AGAINST_LIBM)
# Was not able to figure out if explicit linkage against libm is required.
# Forcing libm linkage. Good idea?
set(NEED_LINKING_AGAINST_LIBM TRUE CACHE BOOL "" FORCE)
endif()
endif()
endif()
endif()
if(NEED_LINKING_AGAINST_LIBM)
set(LIBM_LIB "-lm")
else()
set(LIBM_LIB "")
endif()
option(ENABLE_COVERAGE "Generate a code coverage report using lcov/genhtml." OFF)
option(ENABLE_SANITIZER "Enable ASAN/LSAN/UBSAN." OFF)
option(ENABLE_SANITIZER_THREAD "Enable TSAN (does not work together with ASAN)." OFF)
option(ENABLE_MEMORY_PROFILING "Enable dynamic memory tracking." OFF)
option(ENABLE_ZLIB "Enable zlib support for nDPId (experimental)." OFF)
option(ENABLE_SYSTEMD "Install systemd components." OFF)
option(BUILD_EXAMPLES "Build C examples." ON)
option(ENABLE_DBUS "Build DBus notification example." OFF)
option(ENABLE_CURL "Build influxdb data write example." OFF)
option(BUILD_NDPI "Clone and build nDPI from github." OFF)
if(BUILD_NDPI)
if(APPLE)
message(WARNING "Building libnDPI from CMake is not supported on Apple and may fail.")
endif()
option(BUILD_NDPI_FORCE_GIT_UPDATE "Forcefully instruments nDPI build script to update the git submodule." OFF)
unset(NDPI_NO_PKGCONFIG CACHE)
unset(STATIC_LIBNDPI_INSTALLDIR CACHE)
else()
option(NDPI_NO_PKGCONFIG "Do not use pkgconfig to search for libnDPI." OFF)
if(NDPI_NO_PKGCONFIG)
set(STATIC_LIBNDPI_INSTALLDIR "/opt/libnDPI/usr" CACHE STRING "Path to a installation directory of libnDPI e.g. /opt/libnDPI/usr")
if(STATIC_LIBNDPI_INSTALLDIR STREQUAL "")
message(FATAL_ERROR "STATIC_LIBNDPI_INSTALLDIR can not be an empty string within your configuration!")
endif()
else()
unset(STATIC_LIBNDPI_INSTALLDIR CACHE)
endif()
endif()
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
option(NDPI_WITH_GCRYPT "Link static libndpi library against libgcrypt." OFF)
option(NDPI_WITH_PCRE "Link static libndpi library against libpcre." OFF)
option(NDPI_WITH_MAXMINDDB "Link static libndpi library against libmaxminddb." OFF)
else()
unset(NDPI_WITH_GCRYPT CACHE)
unset(NDPI_WITH_PCRE CACHE)
unset(NDPI_WITH_MAXMINDDB CACHE)
endif()
add_executable(nDPId nDPId.c nio.c utils.c)
add_executable(nDPIsrvd nDPIsrvd.c nio.c utils.c)
add_executable(nDPId-test nDPId-test.c)
add_custom_target(dist)
add_custom_command(
TARGET dist
COMMAND "${CMAKE_SOURCE_DIR}/scripts/make-dist.sh"
)
add_custom_target(daemon)
add_custom_command(
TARGET daemon
COMMAND env nDPIsrvd_ARGS='-C 1024' "${CMAKE_SOURCE_DIR}/scripts/daemon.sh" "$<TARGET_FILE:nDPId>" "$<TARGET_FILE:nDPIsrvd>"
DEPENDS nDPId nDPIsrvd
)
if(CMAKE_CROSSCOMPILING)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endif()
if(BUILD_NDPI)
enable_testing()
add_test(NAME run_tests
COMMAND "${CMAKE_SOURCE_DIR}/test/run_tests.sh"
"${CMAKE_SOURCE_DIR}/libnDPI"
"$<TARGET_FILE:nDPId-test>")
if(NDPI_WITH_PCRE OR NDPI_WITH_MAXMINDDB)
message(WARNING "NDPI_WITH_PCRE or NDPI_WITH_MAXMINDDB enabled.\n"
"${CMAKE_CURRENT_SOURCE_DIR}/test/run_tests.sh or ctest will fail!")
endif()
endif()
if(ENABLE_COVERAGE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} --coverage")
add_custom_target(coverage DEPENDS "${CMAKE_BINARY_DIR}/coverage_report/nDPId/index.html")
add_custom_command(
OUTPUT "${CMAKE_BINARY_DIR}/coverage_report/nDPId/index.html"
COMMAND lcov --directory "${CMAKE_BINARY_DIR}" --directory "${CMAKE_SOURCE_DIR}/libnDPI" --capture --output-file "${CMAKE_BINARY_DIR}/lcov.info"
COMMAND genhtml -o "${CMAKE_BINARY_DIR}/coverage_report" "${CMAKE_BINARY_DIR}/lcov.info"
DEPENDS nDPId nDPId-test nDPIsrvd
)
add_custom_target(coverage-view)
add_custom_command(
TARGET coverage-view
COMMAND cd "${CMAKE_BINARY_DIR}/coverage_report" && python3 -m http.server
DEPENDS "${CMAKE_BINARY_DIR}/coverage_report/nDPId/index.html"
)
endif()
if(ENABLE_SANITIZER)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=leak")
endif()
if(ENABLE_SANITIZER_THREAD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize=alignment -fsanitize=enum -fsanitize=thread")
endif()
if(ENABLE_ZLIB)
set(ZLIB_DEFS "-DENABLE_ZLIB=1")
pkg_check_modules(ZLIB REQUIRED zlib)
endif()
if(ENABLE_DBUS)
pkg_check_modules(DBUS REQUIRED dbus-1)
endif()
if(ENABLE_CURL)
pkg_check_modules(CURL REQUIRED libcurl)
endif()
if(NDPI_WITH_GCRYPT)
message(STATUS "nDPI: Enable GCRYPT")
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --with-local-libgcrypt")
endif()
if(NDPI_WITH_PCRE)
message(STATUS "nDPI: Enable PCRE")
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --with-pcre")
endif()
if(NDPI_WITH_MAXMINDDB)
message(STATUS "nDPI: Enable MAXMINDDB")
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --with-maxminddb")
endif()
if(ENABLE_COVERAGE)
message(STATUS "nDPI: Enable Coverage")
set(NDPI_ADDITIONAL_ARGS "${NDPI_ADDITIONAL_ARGS} --enable-code-coverage")
endif()
execute_process(
COMMAND git describe --tags
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_VERSION ERROR_QUIET)
string(STRIP "${GIT_VERSION}" GIT_VERSION)
if(GIT_VERSION STREQUAL "" OR NOT IS_DIRECTORY "${CMAKE_SOURCE_DIR}/.git")
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "")
set(GIT_VERSION "${CPACK_PACKAGE_VERSION}-pre")
else()
set(GIT_VERSION "${CPACK_PACKAGE_VERSION}-release")
endif()
endif()
set(PKG_VERSION "${CPACK_PACKAGE_VERSION}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
set(NDPID_DEFS -DJSMN_STATIC=1 -DJSMN_STRICT=1 -DJSMN_PARENT_LINKS=1)
set(NDPID_DEPS_INC "${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/dependencies"
"${CMAKE_SOURCE_DIR}/dependencies/jsmn"
"${CMAKE_SOURCE_DIR}/dependencies/uthash/src")
if(CMAKE_CROSSCOMPILING)
add_definitions("-DCROSS_COMPILATION=1")
endif()
if(ENABLE_MEMORY_PROFILING)
message(WARNING "ENABLE_MEMORY_PROFILING should not be used in production environments.")
add_definitions("-DENABLE_MEMORY_PROFILING=1"
"-Duthash_malloc=nDPIsrvd_uthash_malloc"
"-Duthash_free=nDPIsrvd_uthash_free")
else()
set(NDPID_TEST_MPROF_DEFS "-DENABLE_MEMORY_PROFILING=1")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g3 -fno-omit-frame-pointer")
endif()
if(ENABLE_SANITIZER AND ENABLE_SANITIZER_THREAD)
message(FATAL_ERROR "ENABLE_SANITIZER and ENABLE_SANITIZER_THREAD can not be used together!")
endif()
if(BUILD_NDPI)
include(ExternalProject)
ExternalProject_Add(
libnDPI
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libnDPI
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND env
CC=${CMAKE_C_COMPILER}
CXX=false
AR=${CMAKE_AR}
RANLIB=${CMAKE_RANLIB}
PKG_CONFIG=${PKG_CONFIG_EXECUTABLE}
CFLAGS=${CMAKE_C_FLAGS}
LDFLAGS=${CMAKE_MODULE_LINKER_FLAGS}
ADDITIONAL_ARGS=${NDPI_ADDITIONAL_ARGS}
MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}
DEST_INSTALL=${CMAKE_BINARY_DIR}/libnDPI
FORCE_GIT_UPDATE=${BUILD_NDPI_FORCE_GIT_UPDATE}
${CMAKE_CURRENT_SOURCE_DIR}/scripts/get-and-build-libndpi.sh
BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/libnDPI/lib/libndpi.a
BUILD_COMMAND ""
INSTALL_COMMAND ""
BUILD_IN_SOURCE 1)
add_custom_target(clean-libnDPI
COMMAND ${CMAKE_BUILD_TOOL} clean
COMMAND rm -rf ${CMAKE_BINARY_DIR}/libnDPI
COMMAND rm -f ${CMAKE_BINARY_DIR}/libnDPI-prefix/src/libnDPI-stamp/libnDPI-configure
)
set(STATIC_LIBNDPI_INSTALLDIR "${CMAKE_BINARY_DIR}/libnDPI")
add_dependencies(nDPId libnDPI)
add_dependencies(nDPId-test libnDPI)
endif()
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
if(NDPI_WITH_GCRYPT)
find_package(GCRYPT "1.4.2" REQUIRED)
endif()
if(NDPI_WITH_PCRE)
pkg_check_modules(PCRE REQUIRED libpcre>=8.39)
endif()
if(NDPI_WITH_MAXMINDDB)
pkg_check_modules(MAXMINDDB REQUIRED libmaxminddb)
endif()
endif()
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI)
add_definitions("-DLIBNDPI_STATIC=1")
set(STATIC_LIBNDPI_INC "${STATIC_LIBNDPI_INSTALLDIR}/include/ndpi")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
if(EXISTS "${STATIC_LIBNDPI_INSTALLDIR}/lib64")
set(STATIC_LIBNDPI_LIB "${STATIC_LIBNDPI_INSTALLDIR}/lib64/libndpi.a")
else()
set(STATIC_LIBNDPI_LIB "${STATIC_LIBNDPI_INSTALLDIR}/lib/libndpi.a")
endif()
else()
if(EXISTS "${STATIC_LIBNDPI_INSTALLDIR}/lib32")
set(STATIC_LIBNDPI_LIB "${STATIC_LIBNDPI_INSTALLDIR}/lib32/libndpi.a")
else()
set(STATIC_LIBNDPI_LIB "${STATIC_LIBNDPI_INSTALLDIR}/lib/libndpi.a")
endif()
endif()
if(STATIC_LIBNDPI_INSTALLDIR AND NOT BUILD_NDPI)
if(NOT EXISTS "${STATIC_LIBNDPI_INC}" OR NOT EXISTS "${STATIC_LIBNDPI_LIB}")
message(FATAL_ERROR "Include directory \"${STATIC_LIBNDPI_INC}\" or\n"
"static library \"${STATIC_LIBNDPI_LIB}\" does not exist!")
endif()
endif()
unset(DEFAULT_NDPI_INCLUDE CACHE)
unset(pkgcfg_lib_NDPI_ndpi CACHE)
else()
if(NOT NDPI_NO_PKGCONFIG)
pkg_check_modules(NDPI REQUIRED libndpi>=4.7.0)
if(NOT pkgcfg_lib_NDPI_ndpi)
find_package(NDPI "4.8.0" REQUIRED)
endif()
unset(STATIC_LIBNDPI_INC CACHE)
unset(STATIC_LIBNDPI_LIB CACHE)
endif()
set(DEFAULT_NDPI_INCLUDE ${NDPI_INCLUDE_DIRS})
endif()
pkg_check_modules(PCAP libpcap>=1.9.0) # no *.pc file before 1.9.0
if(NOT pkgcfg_lib_PCAP_pcap)
pkg_check_modules(PCAP libpcap>=1.8.1) # seems like some distributions provide their own *.pc file for 1.8.1 (e.g. Ubuntu-18.04)
endif()
if(NOT pkgcfg_lib_PCAP_pcap)
find_package(PCAP "1.9.0" REQUIRED)
endif()
target_compile_options(nDPId PRIVATE "-pthread")
target_compile_definitions(nDPId PRIVATE -D_GNU_SOURCE=1 -DPKG_VERSION=\"${PKG_VERSION}\" -DGIT_VERSION=\"${GIT_VERSION}\" ${NDPID_DEFS} ${EPOLL_DEFS} ${ZLIB_DEFS})
target_include_directories(nDPId PRIVATE "${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" ${NDPID_DEPS_INC})
target_link_libraries(nDPId "${STATIC_LIBNDPI_LIB}" "${pkgcfg_lib_PCAP_pcap}" "${pkgcfg_lib_NDPI_ndpi}"
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}" "${pkgcfg_lib_ZLIB_z}"
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}" "${LIBM_LIB}"
"-pthread")
target_compile_definitions(nDPIsrvd PRIVATE -D_GNU_SOURCE=1 -DPKG_VERSION=\"${PKG_VERSION}\" -DGIT_VERSION=\"${GIT_VERSION}\" ${NDPID_DEFS} ${EPOLL_DEFS})
target_include_directories(nDPIsrvd PRIVATE ${NDPID_DEPS_INC})
target_include_directories(nDPId-test PRIVATE ${NDPID_DEPS_INC})
target_compile_options(nDPId-test PRIVATE "-Wno-unused-function" "-pthread")
target_compile_definitions(nDPId-test PRIVATE -D_GNU_SOURCE=1 -DNO_MAIN=1 -DPKG_VERSION=\"${PKG_VERSION}\" -DGIT_VERSION=\"${GIT_VERSION}\"
${NDPID_DEFS} ${EPOLL_DEFS} ${ZLIB_DEFS} ${NDPID_TEST_MPROF_DEFS})
target_include_directories(nDPId-test PRIVATE
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" ${NDPID_DEPS_INC})
target_link_libraries(nDPId-test "${STATIC_LIBNDPI_LIB}" "${pkgcfg_lib_PCAP_pcap}" "${pkgcfg_lib_NDPI_ndpi}"
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}" "${pkgcfg_lib_ZLIB_z}"
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}" "${LIBM_LIB}"
"-pthread")
if(BUILD_EXAMPLES)
add_executable(nDPIsrvd-collectd examples/c-collectd/c-collectd.c utils.c)
if(BUILD_NDPI)
add_dependencies(nDPIsrvd-collectd libnDPI)
endif()
target_compile_definitions(nDPIsrvd-collectd PRIVATE ${NDPID_DEFS})
target_include_directories(nDPIsrvd-collectd PRIVATE
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" "${CMAKE_SOURCE_DIR}" ${NDPID_DEPS_INC})
add_executable(nDPIsrvd-captured examples/c-captured/c-captured.c utils.c)
if(BUILD_NDPI)
add_dependencies(nDPIsrvd-captured libnDPI)
endif()
target_compile_definitions(nDPIsrvd-captured PRIVATE ${NDPID_DEFS})
target_include_directories(nDPIsrvd-captured PRIVATE
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" "${CMAKE_SOURCE_DIR}" ${NDPID_DEPS_INC})
target_link_libraries(nDPIsrvd-captured "${pkgcfg_lib_PCAP_pcap}" "${pkgcfg_lib_NDPI_ndpi}"
"${pkgcfg_lib_PCRE_pcre}" "${pkgcfg_lib_MAXMINDDB_maxminddb}"
"${GCRYPT_LIBRARY}" "${GCRYPT_ERROR_LIBRARY}" "${PCAP_LIBRARY}")
add_executable(nDPIsrvd-json-dump examples/c-json-stdout/c-json-stdout.c)
target_compile_definitions(nDPIsrvd-json-dump PRIVATE ${NDPID_DEFS})
target_include_directories(nDPIsrvd-json-dump PRIVATE ${NDPID_DEPS_INC})
add_executable(nDPIsrvd-analysed examples/c-analysed/c-analysed.c utils.c)
target_compile_definitions(nDPIsrvd-analysed PRIVATE ${NDPID_DEFS})
target_include_directories(nDPIsrvd-analysed PRIVATE ${NDPID_DEPS_INC})
add_executable(nDPIsrvd-simple examples/c-simple/c-simple.c)
target_compile_definitions(nDPIsrvd-simple PRIVATE ${NDPID_DEFS})
target_include_directories(nDPIsrvd-simple PRIVATE ${NDPID_DEPS_INC})
if(ENABLE_COVERAGE)
add_dependencies(coverage nDPIsrvd-analysed nDPIsrvd-collectd nDPIsrvd-captured nDPIsrvd-json-dump nDPIsrvd-simple)
endif()
if(ENABLE_DBUS)
add_executable(nDPIsrvd-notifyd examples/c-notifyd/c-notifyd.c utils.c)
if(BUILD_NDPI)
add_dependencies(nDPIsrvd-notifyd libnDPI)
endif()
target_include_directories(nDPIsrvd-notifyd PRIVATE
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" "${CMAKE_SOURCE_DIR}" "${NDPID_DEPS_INC}"
"${DBUS_INCLUDE_DIRS}")
target_link_libraries(nDPIsrvd-notifyd "${DBUS_LIBRARIES}")
install(TARGETS nDPIsrvd-notifyd DESTINATION bin)
endif()
if(ENABLE_CURL)
add_executable(nDPIsrvd-influxd examples/c-influxd/c-influxd.c utils.c)
if(BUILD_NDPI)
add_dependencies(nDPIsrvd-influxd libnDPI)
endif()
target_include_directories(nDPIsrvd-influxd PRIVATE
"${STATIC_LIBNDPI_INC}" "${DEFAULT_NDPI_INCLUDE}" "${CMAKE_SOURCE_DIR}" "${NDPID_DEPS_INC}"
"${CURL_INCLUDE_DIRS}")
target_link_libraries(nDPIsrvd-influxd "${CURL_LIBRARIES}")
install(TARGETS nDPIsrvd-influxd DESTINATION bin)
endif()
install(TARGETS nDPIsrvd-analysed nDPIsrvd-collectd nDPIsrvd-captured nDPIsrvd-json-dump nDPIsrvd-simple DESTINATION bin)
install(FILES examples/c-collectd/plugin_nDPIsrvd.conf examples/c-collectd/rrdgraph.sh DESTINATION share/nDPId/nDPIsrvd-collectd)
install(DIRECTORY examples/c-collectd/www DESTINATION share/nDPId/nDPIsrvd-collectd)
endif()
if(ENABLE_SYSTEMD)
configure_file(packages/systemd/ndpisrvd.service.in ndpisrvd.service @ONLY)
configure_file(packages/systemd/ndpid@.service.in ndpid@.service @ONLY)
install(FILES packages/systemd/default.cfg DESTINATION etc/default RENAME ndpid)
install(FILES "${CMAKE_BINARY_DIR}/ndpisrvd.service" DESTINATION lib/systemd/system)
install(FILES "${CMAKE_BINARY_DIR}/ndpid@.service" DESTINATION lib/systemd/system)
endif()
install(FILES config.h
dependencies/nDPIsrvd.h
dependencies/jsmn/jsmn.h
dependencies/uthash/src/utarray.h
dependencies/uthash/src/uthash.h
dependencies/uthash/src/utlist.h
dependencies/uthash/src/utringbuffer.h
dependencies/uthash/src/utstack.h
dependencies/uthash/src/utstring.h
DESTINATION include/nDPId)
install(TARGETS nDPId DESTINATION sbin)
install(TARGETS nDPIsrvd nDPId-test DESTINATION bin)
if(BUILD_EXAMPLES)
install(FILES dependencies/nDPIsrvd.py examples/py-flow-dashboard/plotly_dash.py
DESTINATION share/nDPId)
install(FILES examples/py-flow-info/flow-info.py
DESTINATION bin RENAME nDPIsrvd-flow-info.py
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES examples/py-flow-dashboard/flow-dash.py
DESTINATION bin RENAME nDPIsrvd-flow-dash.py
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES examples/py-json-stdout/json-stdout.py
DESTINATION bin RENAME nDPIsrvd-json-stdout.py
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES examples/py-schema-validation/py-schema-validation.py
DESTINATION bin RENAME nDPIsrvd-schema-validation.py
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES examples/py-semantic-validation/py-semantic-validation.py
DESTINATION bin RENAME nDPIsrvd-semantic-validation.py
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES examples/py-machine-learning/sklearn-random-forest.py
DESTINATION bin RENAME nDPIsrvd-sklearn.py
PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
endif()
install(FILES schema/error_event_schema.json schema/daemon_event_schema.json
schema/flow_event_schema.json schema/packet_event_schema.json DESTINATION share/nDPId/json-schema)
message(STATUS "--------------------------")
message(STATUS "nDPId GIT_VERSION........: ${GIT_VERSION}")
message(STATUS "Cross Compilation........: ${CMAKE_CROSSCOMPILING}")
message(STATUS "CMAKE_BUILD_TYPE.........: ${CMAKE_BUILD_TYPE}")
message(STATUS "CMAKE_C_FLAGS............: ${CMAKE_C_FLAGS}")
message(STATUS "NDPID_DEFS...............: ${NDPID_DEFS}")
message(STATUS "FORCE_POLL...............: ${FORCE_POLL}")
message(STATUS "ENABLE_COVERAGE..........: ${ENABLE_COVERAGE}")
message(STATUS "ENABLE_SANITIZER.........: ${ENABLE_SANITIZER}")
message(STATUS "ENABLE_SANITIZER_THREAD..: ${ENABLE_SANITIZER_THREAD}")
message(STATUS "ENABLE_MEMORY_PROFILING..: ${ENABLE_MEMORY_PROFILING}")
message(STATUS "ENABLE_ZLIB..............: ${ENABLE_ZLIB}")
if(STATIC_LIBNDPI_INSTALLDIR)
message(STATUS "STATIC_LIBNDPI_INSTALLDIR: ${STATIC_LIBNDPI_INSTALLDIR}")
endif()
message(STATUS "BUILD_NDPI...............: ${BUILD_NDPI}")
message(STATUS "ENABLE_DBUS..............: ${ENABLE_DBUS}")
message(STATUS "ENABLE_CURL..............: ${ENABLE_CURL}")
if(BUILD_NDPI)
message(STATUS "NDPI_ADDITIONAL_ARGS.....: ${NDPI_ADDITIONAL_ARGS}")
endif()
message(STATUS "NDPI_NO_PKGCONFIG........: ${NDPI_NO_PKGCONFIG}")
message(STATUS "--------------------------")
if(STATIC_LIBNDPI_INSTALLDIR OR BUILD_NDPI OR NDPI_NO_PKGCONFIG)
message(STATUS "- STATIC_LIBNDPI_INC....: ${STATIC_LIBNDPI_INC}")
message(STATUS "- STATIC_LIBNDPI_LIB....: ${STATIC_LIBNDPI_LIB}")
message(STATUS "- NDPI_WITH_GCRYPT......: ${NDPI_WITH_GCRYPT}")
message(STATUS "- NDPI_WITH_PCRE........: ${NDPI_WITH_PCRE}")
message(STATUS "- NDPI_WITH_MAXMINDDB...: ${NDPI_WITH_MAXMINDDB}")
endif()
if(NOT STATIC_LIBNDPI_INSTALLDIR AND NOT BUILD_NDPI)
message(STATUS "- DEFAULT_NDPI_INCLUDE..: ${DEFAULT_NDPI_INCLUDE}")
endif()
if(NOT NDPI_NO_PKGCONFIG)
message(STATUS "- pkgcfg_lib_NDPI_ndpi..: ${pkgcfg_lib_NDPI_ndpi}")
endif()
message(STATUS "--------------------------")

674
COPYING
View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -1,21 +0,0 @@
FROM ubuntu:22.04 AS builder
WORKDIR /root
RUN apt-get -y update && apt-get install -y --no-install-recommends autoconf automake build-essential ca-certificates wget unzip git make cmake pkg-config libpcap-dev autoconf libtool && apt-get clean
RUN git clone https://github.com/utoni/nDPId.git
WORKDIR /root/nDPId
RUN cmake -S . -B build -DBUILD_NDPI=ON && cmake --build build --verbose
FROM ubuntu:22.04
USER root
WORKDIR /
COPY --from=builder /root/nDPId/build/nDPId /usr/sbin/nDPId
COPY --from=builder /root/nDPId/build/nDPIsrvd /usr/bin/nDPIsrvd
RUN apt-get -y update && apt-get install -y --no-install-recommends libpcap-dev && apt-get clean
USER nobody
RUN /usr/bin/nDPIsrvd -h || { RC=$?; test ${RC} -eq 1; }
RUN /usr/sbin/nDPId -h || { RC=$?; test ${RC} -eq 1; }

34
Makefile Normal file
View File

@@ -0,0 +1,34 @@
CC = gcc
CFLAGS += -Wall -Wextra $(EXTRA_CFLAGS)
LIBS += -pthread -lpcap -lm
ifneq ($(CUSTOM_LIBNDPI),)
LIBS += '$(CUSTOM_LIBNDPI)'
CFLAGS += '-I$(shell dirname $(CUSTOM_LIBNDPI))/../include'
ifeq ($(findstring $*.so, $(CUSTOM_LIBNDPI)),.so)
CFLAGS += -Wl,-rpath='$(shell dirname $(CUSTOM_LIBNDPI))'
endif
else
LIBS += -lndpi
endif
ifeq ($(ENABLE_DEBUG),yes)
CFLAGS += -Og -g3
endif
ifeq ($(ENABLE_SANITIZER),yes)
CFLAGS += -fsanitize=address -fsanitize=undefined -fsanitize=leak
LIBS += -lasan -lubsan
endif
ifeq ($(VERBOSE),yes)
CFLAGS += -DVERBOSE
endif
RM = rm -f
main: main.c
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(LIBS)
clean:
$(RM) main

340
README.md
View File

@@ -1,340 +0,0 @@
[![Build](https://github.com/utoni/nDPId/actions/workflows/build.yml/badge.svg)](https://github.com/utoni/nDPId/actions/workflows/build.yml)
[![Gitlab-CI](https://gitlab.com/utoni/nDPId/badges/main/pipeline.svg)](https://gitlab.com/utoni/nDPId/-/pipelines)
[![Circle-CI](https://circleci.com/gh/utoni/nDPId.svg?style=shield "Circle-CI")](https://app.circleci.com/pipelines/github/utoni/nDPId)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=lnslbrty_nDPId&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=lnslbrty_nDPId)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=lnslbrty_nDPId&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=lnslbrty_nDPId)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=lnslbrty_nDPId&metric=bugs)](https://sonarcloud.io/summary/new_code?id=lnslbrty_nDPId)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=lnslbrty_nDPId&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=lnslbrty_nDPId)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=lnslbrty_nDPId&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=lnslbrty_nDPId)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=lnslbrty_nDPId&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=lnslbrty_nDPId)
![Docker Automated build](https://img.shields.io/docker/automated/utoni/ndpid)
# References
[ntop Webinar 2022](https://www.ntop.org/webinar/ntop-webinar-on-dec-14th-community-meeting-and-future-plans/)
[ntopconf 2023](https://www.ntop.org/ntopconf2023/)
# Disclaimer
Please respect & protect the privacy of others.
The purpose of this software is not to spy on others, but to detect network anomalies and malicious traffic.
# Abstract
nDPId is a set of daemons and tools to capture, process and classify network traffic.
Its minimal dependencies (besides a half-way modern C library and POSIX threads) are libnDPI (>=4.8.0 or current github dev branch) and libpcap.
The daemon `nDPId` is capable of multithreading for packet processing, but w/o mutexes for performance reasons.
Instead, synchronization is achieved by a packet distribution mechanism.
To balance the workload to all threads (more or less) equally, a unique identifier represented as hash value is calculated using a 3-tuple consisting of: IPv4/IPv6 src/dst address; IP header value of the layer4 protocol; and (for TCP/UDP) src/dst port. Other protocols e.g. ICMP/ICMPv6 lack relevance for DPI, thus nDPId does not distinguish between different ICMP/ICMPv6 flows coming from the same host. This saves memory and performance, but might change in the future.
`nDPId` uses libnDPI's JSON serialization interface to generate a JSON messages for each event it receives from the library and which it then sends out to a UNIX-socket (default: `/tmp/ndpid-collector.sock` ). From such a socket, `nDPIsrvd` (or other custom applications) can retrieve incoming JSON-messages and further proceed working/distributing messages to higher-level applications.
Unfortunately, `nDPIsrvd` does not yet support any encryption/authentication for TCP connections (TODO!).
# Architecture
This project uses a kind of microservice architecture.
```text
connect to UNIX socket [1] connect to UNIX/TCP socket [2]
_______________________ | | __________________________
| "producer" |___| |___| "consumer" |
|---------------------| _____________________________ |------------------------|
| | | nDPIsrvd | | |
| nDPId --- Thread 1 >| ---> |> | <| ---> |< example/c-json-stdout |
| (eth0) `- Thread 2 >| ---> |> collector | distributor <| ---> |________________________|
| `- Thread N >| ---> |> >>> forward >>> <| ---> | |
|_____________________| ^ |____________|______________| ^ |< example/py-flow-info |
| | | | |________________________|
| nDPId --- Thread 1 >| `- send serialized data [1] | | |
| (eth1) `- Thread 2 >| | |< example/... |
| `- Thread N >| receive serialized data [2] -' |________________________|
|_____________________|
```
where:
* `nDPId` capture traffic, extract traffic data (with libnDPI) and send a JSON-serialized output stream to an already existing UNIX-socket;
* `nDPIsrvd`:
* create and manage an "incoming" UNIX-socket (ref [1] above), to fetch data from a local `nDPId`;
* apply a buffering logic to received data;
* create and manage an "outgoing" UNIX or TCP socket (ref [2] above) to relay matched events
to connected clients
* `consumers` are common/custom applications being able to receive selected flows/events, via both UNIX-socket or TCP-socket.
# JSON stream format
JSON messages streamed by both `nDPId` and `nDPIsrvd` are presented with:
* a 5-digit-number describing (as decimal number) the **entire** JSON message including the newline `\n` at the end;
* the JSON messages
```text
[5-digit-number][JSON message]
```
as with the following example:
```text
01223{"flow_event_id":7,"flow_event_name":"detection-update","thread_id":12,"packet_id":307,"source":"wlan0",[...]}
00458{"packet_event_id":2,"packet_event_name":"packet-flow","thread_id":11,"packet_id":324,"source":"wlan0",[...]]}
00572{"flow_event_id":1,"flow_event_name":"new","thread_id":11,"packet_id":324,"source":"wlan0",[...]}
```
The full stream of `nDPId` generated JSON-events can be retrieved directly from `nDPId`, without relying on `nDPIsrvd`, by providing a properly managed UNIX-socket.
Technical details about the JSON-message format can be obtained from the related `.schema` file included in the `schema` directory
# Events
`nDPId` generates JSON messages whereby each string is assigned to a certain event.
Those events specify the contents (key-value-pairs) of the JSON message.
They are divided into four categories, each with a number of subevents.
## Error Events
They are 17 distinct events, indicating that layer2 or layer3 packet processing failed or not enough flow memory available:
1. Unknown datalink layer packet
2. Unknown L3 protocol
3. Unsupported datalink layer
4. Packet too short
5. Unknown packet type
6. Packet header invalid
7. IP4 packet too short
8. Packet smaller than IP4 header:
9. nDPI IPv4/L4 payload detection failed
10. IP6 packet too short
11. Packet smaller than IP6 header
12. nDPI IPv6/L4 payload detection failed
13. TCP packet smaller than expected
14. UDP packet smaller than expected
15. Captured packet size is smaller than expected packet size
16. Max flows to track reached
17. Flow memory allocation failed
Detailed JSON-schema is available [here](schema/error_event_schema.json)
## Daemon Events
There are 4 distinct events indicating startup/shutdown or status events as well as a reconnect event if there was a previous connection failure (collector):
1. init: `nDPId` startup
2. reconnect: (UNIX) socket connection lost previously and was established again
3. shutdown: `nDPId` terminates gracefully
4. status: statistics about the daemon itself e.g. memory consumption, zLib compressions (if enabled)
Detailed JSON-schema is available [here](schema/daemon_event_schema.json)
## Packet Events
There are 2 events containing base64 encoded packet payloads either belonging to a flow or not:
1. packet: does not belong to any flow
2. packet-flow: belongs to a flow e.g. TCP/UDP or ICMP
Detailed JSON-schema is available [here](schema/packet_event_schema.json)
## Flow Events
There are 9 distinct events related to a flow:
1. new: a new TCP/UDP/ICMP flow seen which will be tracked
2. end: a TCP connection terminates
3. idle: a flow timed out, because there was no packet on the wire for a certain amount of time
4. update: inform nDPIsrvd or other apps about a long-lasting flow, whose detection was finished a long time ago but is still active
5. analyse: provide some information about extracted features of a flow (Experimental; disabled per default, enable with `-A`)
6. guessed: `libnDPI` was not able to reliably detect a layer7 protocol and falls back to IP/Port based detection
7. detected: `libnDPI` sucessfully detected a layer7 protocol
8. detection-update: `libnDPI` dissected more layer7 protocol data (after detection already done)
9. not-detected: neither detected nor guessed
Detailed JSON-schema is available [here](schema/flow_event_schema.json). Also, a graphical representation of *Flow Events* timeline is available [here](schema/flow_events_diagram.png).
# Flow States
A flow can have three different states while it is been tracked by `nDPId`.
1. skipped: the flow will be tracked, but no detection will happen to safe memory. See command line argument `-I` and `-E`
2. finished: detection finished and the memory used for the detection is freed
3. info: detection is in progress and all flow memory required for `libnDPI` is allocated (this state consumes most memory)
# Build (CMake)
`nDPId` build system is based on [CMake](https://cmake.org/)
```shell
git clone https://github.com/utoni/nDPId.git
[...]
cd ndpid
mkdir build
cd build
cmake ..
[...]
make
```
see below for a full/test live-session
![](examples/ndpid_install_and_run.gif)
Based on your build environment and/or desiderata, you could need:
```shell
mkdir build
cd build
ccmake ..
```
or to build with a staticially linked libnDPI:
```shell
mkdir build
cd build
cmake .. -DSTATIC_LIBNDPI_INSTALLDIR=[path/to/your/libnDPI/installdir]
```
If you use the latter, make sure that you've configured libnDPI with `./configure --prefix=[path/to/your/libnDPI/installdir]`
and remember to set the all-necessary CMake variables to link against shared libraries used by your nDPI build.
e.g.:
```shell
mkdir build
cd build
cmake .. -DSTATIC_LIBNDPI_INSTALLDIR=[path/to/your/libnDPI/installdir] -DNDPI_WITH_GCRYPT=ON -DNDPI_WITH_PCRE=OFF -DNDPI_WITH_MAXMINDDB=OFF
```
Or let a shell script do the work for you:
```shell
mkdir build
cd build
cmake .. -DBUILD_NDPI=ON
```
The CMake cache variable `-DBUILD_NDPI=ON` builds a version of `libnDPI` residing as a git submodule in this repository.
# run
As mentioned above, in order to run `nDPId`, a UNIX-socket needs to be provided in order to stream our related JSON-data.
Such a UNIX-socket can be provided by both the included `nDPIsrvd` daemon, or, if you simply need a quick check, with the [ncat](https://nmap.org/book/ncat-man.html) utility, with a simple `ncat -U /tmp/listen.sock -l -k`. Remember that OpenBSD `netcat` is not able to handle multiple connections reliably.
Once the socket is ready, you can run `nDPId` capturing and analyzing your own traffic, with something similar to: `sudo nDPId -c /tmp/listen.sock`
If you're using OpenBSD `netcat`, you need to run: `sudo nDPId -c /tmp/listen.sock -o max-reader-threads=1`
Make sure that the UNIX socket is accessible by the user (see -u) to whom nDPId changes to, default: nobody.
Of course, both `ncat` and `nDPId` need to point to the same UNIX-socket (`nDPId` provides the `-c` option, exactly for this. By default, `nDPId` refers to `/tmp/ndpid-collector.sock`, and the same default-path is also used by `nDPIsrvd` for the incoming socket).
Give `nDPId` some real-traffic. You can capture your own traffic, with something similar to:
```shell
socat -u UNIX-Listen:/tmp/listen.sock,fork - # does the same as `ncat`
sudo chown nobody:nobody /tmp/listen.sock # default `nDPId` user/group, see `-u` and `-g`
sudo ./nDPId -c /tmp/listen.sock -l
```
`nDPId` supports also UDP collector endpoints:
```shell
nc -d -u 127.0.0.1 7000 -l -k
sudo ./nDPId -c 127.0.0.1:7000 -l
```
or you can generate a nDPId-compatible JSON dump with:
```shell
./nDPId-test [path-to-a-PCAP-file]
```
You can also automatically fire both `nDPId` and `nDPIsrvd` automatically, with:
Daemons:
```shell
make -C [path-to-a-build-dir] daemon
```
Or a manual approach with:
```shell
./nDPIsrvd -d
sudo ./nDPId -d
```
or for a usage printout:
```shell
./nDPIsrvd -h
./nDPId -h
```
And why not a flow-info example?
```shell
./examples/py-flow-info/flow-info.py
```
or
```shell
./nDPIsrvd-json-dump
```
or anything below `./examples`.
# nDPId tuning
It is possible to change `nDPId` internals w/o recompiling by using `-o subopt=value`.
But be careful: changing the default values may render `nDPId` useless and is not well tested.
Suboptions for `-o`:
Format: `subopt` (unit, comment): description
* `max-flows-per-thread` (N, caution advised): affects max. memory usage
* `max-idle-flows-per-thread` (N, safe): max. allowed idle flows whose memory gets freed after `flow-scan-interval`
* `max-reader-threads` (N, safe): amount of packet processing threads, every thread can have a max. of `max-flows-per-thread` flows
* `daemon-status-interval` (ms, safe): specifies how often daemon event `status` is generated
* `compression-scan-interval` (ms, untested): specifies how often `nDPId` scans for inactive flows ready for compression
* `compression-flow-inactivity` (ms, untested): the shortest period of time elapsed before `nDPId` considers compressing a flow that neither sent nor received any data
* `flow-scan-interval` (ms, safe): min. amount of time after which `nDPId` scans for idle or long-lasting flows
* `generic-max-idle-time` (ms, untested): time after which a non TCP/UDP/ICMP flow times out
* `icmp-max-idle-time` (ms, untested): time after which an ICMP flow times out
* `udp-max-idle-time` (ms, caution advised): time after which an UDP flow times out
* `tcp-max-idle-time` (ms, caution advised): time after which a TCP flow times out
* `tcp-max-post-end-flow-time` (ms, caution advised): a TCP flow that received a FIN or RST waits this amount of time before flow tracking stops and the flow memory is freed
* `max-packets-per-flow-to-send` (N, safe): max. `packet-flow` events generated for the first N packets of each flow
* `max-packets-per-flow-to-process` (N, caution advised): max. amount of packets processed by `libnDPI`
* `max-packets-per-flow-to-analyze` (N, safe): max. packets to analyze before sending an `analyse` event, requires `-A`
* `error-event-threshold-n` (N, safe): max. error events to send until threshold time has passed
* `error-event-threshold-time` (N, safe): time after which the error event threshold resets
# test
The recommended way to run integration / diff tests:
```shell
mkdir build
cd build
cmake .. -DBUILD_NDPI=ON
make nDPId-test test
```
Alternatively you can run some integration tests manually:
`./test/run_tests.sh [/path/to/libnDPI/root/directory] [/path/to/nDPId-test]`
e.g.:
`./test/run_tests.sh [${HOME}/git/nDPI] [${HOME}/git/nDPId/build/nDPId-test]`
Remember that all test results are tied to a specific libnDPI commit hash
as part of the `git submodule`. Using `test/run_tests.sh` for other commit hashes
will most likely result in PCAP diffs.
Why not use `examples/py-flow-dashboard/flow-dash.py` to visualize nDPId's output.
# Contributors
Special thanks to Damiano Verzulli ([@verzulli](https://github.com/verzulli)) from [GARRLab](https://www.garrlab.it) for providing server and test infrastructure.

19
TODO.md
View File

@@ -1,19 +0,0 @@
# TODOs
1.7:
* let nDPIsrvd (collector) connect to other nDPIsrvd instances (as distributor)
* nDPIsrvd GnuTLS support for TCP/IP distributor connections
* PF\_RING integration
2.0.0:
* switch to semantic versioning for the greater good ;)
no release plan:
* merge flow end/idle event into idle event (end is not really useful..)
* provide a shared library for C / C++ for distributor application developers
* improve UDP/TCP timeout handling by reading netfilter conntrack timeouts from /proc (or just read conntrack table entries)
* detect interface / timeout changes and apply them to nDPId
* switch to MIT or BSD License

View File

@@ -1,28 +0,0 @@
# - Check if the system supports epoll.
# CHECK_EPOLL(<var>)
# <var> - variable to store the result
# (1 for success, empty for failure)
#=============================================================================
# This software is in the public domain, furnished "as is", without technical
# support, and with no warranty, express or implied, as to its usefulness for
# any purpose.
#=============================================================================
macro(CHECK_EPOLL VARIABLE)
if(UNIX)
if("${VARIABLE}" MATCHES "^${VARIABLE}$")
message(STATUS "Check if the system supports epoll")
include(CheckSymbolExists)
check_symbol_exists(epoll_create "sys/epoll.h" EPOLL_PROTOTYPE_EXISTS)
if(EPOLL_PROTOTYPE_EXISTS)
message(STATUS "Check if the system supports epoll - yes")
set(${VARIABLE} 1 CACHE INTERNAL "Result of CHECK_EPOLL" FORCE)
else(EPOLL_PROTOTYPE_EXISTS)
message(STATUS "Check if the system supports epoll - no")
set(${VARIABLE} "" CACHE INTERNAL "Result of CHECK_EPOLL" FORCE)
endif(EPOLL_PROTOTYPE_EXISTS)
endif("${VARIABLE}" MATCHES "^${VARIABLE}$")
endif(UNIX)
endmacro(CHECK_EPOLL)

View File

@@ -1,69 +0,0 @@
#
# - Find gcrypt
# Find the native GCRYPT includes and library
#
# GCRYPT_INCLUDE_DIRS - where to find gcrypt.h, etc.
# GCRYPT_LIBRARIES - List of libraries when using gcrypt.
# GCRYPT_FOUND - True if gcrypt found.
# GCRYPT_DLL_DIR - (Windows) Path to the Libgcrypt DLLs.
# GCRYPT_DLLS - (Windows) List of required Libgcrypt DLLs.
if(GCRYPT_INCLUDE_DIRS)
# Already in cache, be silent
set(GCRYPT_FIND_QUIETLY TRUE)
endif()
find_path(GCRYPT_INCLUDE_DIR gcrypt.h
HINTS
"${GCRYPT_HINTS}/include"
)
find_library(GCRYPT_LIBRARY
NAMES gcrypt libgcrypt-20
HINTS "${GCRYPT_HINTS}/bin")
# libgpg-error6-0 is used in libgcrypt-1.7.6-win??ws (built from source).
# libgpg-error-0 is used in libgcrypt-1.8.3-win??ws (from Debian).
find_library(GCRYPT_ERROR_LIBRARY
NAMES gpg-error libgpg-error-0 libgpg-error6-0
HINTS "${GCRYPT_HINTS}/bin")
# Try to retrieve version from header if found (available since libgcrypt 1.3.0)
if(GCRYPT_INCLUDE_DIR)
set(_version_regex "^#define[ \t]+GCRYPT_VERSION[ \t]+\"([^\"]+)\".*")
file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" GCRYPT_VERSION REGEX "${_version_regex}")
string(REGEX REPLACE "${_version_regex}" "\\1" GCRYPT_VERSION "${GCRYPT_VERSION}")
unset(_version_regex)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GCRYPT
REQUIRED_VARS GCRYPT_LIBRARY GCRYPT_INCLUDE_DIR
VERSION_VAR GCRYPT_VERSION)
if(GCRYPT_FOUND)
set(GCRYPT_LIBRARIES ${GCRYPT_LIBRARY} ${GCRYPT_ERROR_LIBRARY})
set(GCRYPT_INCLUDE_DIRS ${GCRYPT_INCLUDE_DIR})
if(WIN32)
set(GCRYPT_DLL_DIR "${GCRYPT_HINTS}/bin"
CACHE PATH "Path to the Libgcrypt DLLs"
)
file(GLOB _gcrypt_dlls RELATIVE "${GCRYPT_DLL_DIR}"
"${GCRYPT_DLL_DIR}/libgcrypt-*.dll"
"${GCRYPT_DLL_DIR}/libgpg-error*.dll"
)
set(GCRYPT_DLLS ${_gcrypt_dlls}
# We're storing filenames only. Should we use STRING instead?
CACHE FILEPATH "Libgcrypt DLL list"
)
mark_as_advanced(GCRYPT_DLL_DIR GCRYPT_DLLS)
endif()
else()
set(GCRYPT_LIBRARIES)
set(GCRYPT_INCLUDE_DIRS)
set(GCRYPT_DLL_DIR)
set(GCRYPT_DLLS)
endif()
mark_as_advanced(GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIRS)

View File

@@ -1,273 +0,0 @@
#
# - Find libpcap
# Find the native PCAP includes and library
#
# PCAP_INCLUDE_DIRS - where to find pcap.h, etc.
# PCAP_LIBRARIES - List of libraries when using pcap.
# PCAP_FOUND - True if pcap found.
#
# First, try pkg-config on platforms other than Windows.
#
if(NOT WIN32)
find_package(PkgConfig)
pkg_search_module(PC_PCAP libpcap)
endif()
if(NOT PC_PCAP_FOUND AND NOT WIN32)
#
# That didn't work. Try to retrieve hints from pcap-config.
# Do not use it on Windows as pcap-config is a shell script.
#
find_program(PCAP_CONFIG pcap-config)
if(PCAP_CONFIG)
#
# We have pcap-config; use it.
#
# First, get the include directory.
#
execute_process(COMMAND "${PCAP_CONFIG}" "--cflags"
RESULT_VARIABLE PCAP_CONFIG_RESULT
OUTPUT_VARIABLE PCAP_CONFIG_OUTPUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT PCAP_CONFIG_RESULT EQUAL 0)
message(FATAL_ERROR "pcap-config --cflags failed")
endif()
#
# Assumes there's exactly one -I flag in the output
# of pcap-config --cflags. That *should* be the case.
# Note that the hint might be bogus, on macOS it could be
# -I/usr/local/include even though the header isn't
# there (it may be under /usr/include or it may be
# buried in the Xcode app bundle).
#
string(REGEX REPLACE "^-I" "" PCAP_CONFIG_INCLUDE_DIRS "${PCAP_CONFIG_OUTPUT}")
# Now, get the library search path.
execute_process(COMMAND "${PCAP_CONFIG}" "--libs"
RESULT_VARIABLE PCAP_CONFIG_RESULT
OUTPUT_VARIABLE PCAP_CONFIG_OUTPUT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT PCAP_CONFIG_RESULT EQUAL 0)
message(FATAL_ERROR "pcap-config --libs failed")
endif()
separate_arguments(LIBS_LIST UNIX_COMMAND ${PCAP_CONFIG_OUTPUT})
set(PCAP_CONFIG_LIBRARY_DIRS "")
foreach(_arg IN LISTS LIBS_LIST)
# At most one -L path is expected for -lpcap.
if(_arg MATCHES "^-L")
string(REGEX REPLACE "^-L" "" _dir ${_arg})
list(APPEND PCAP_CONFIG_LIBRARY_DIRS ${_dir})
endif()
endforeach()
if(UNIX AND CMAKE_FIND_LIBRARY_SUFFIXES STREQUAL ".a")
# Now, get the library directories and libraries for static linking.
# (XXX - what about AIX?)
execute_process(COMMAND "${PCAP_CONFIG}" "--libs" "--static"
RESULT_VARIABLE PCAP_CONFIG_RESULT
OUTPUT_VARIABLE PCAP_CONFIG_OUTPUT
)
if(NOT PCAP_CONFIG_RESULT EQUAL 0)
message(FATAL_ERROR "pcap-config --libs --static failed")
endif()
separate_arguments(LIBS_LIST UNIX_COMMAND ${PCAP_CONFIG_OUTPUT})
set(PCAP_CONFIG_STATIC_LIBRARY_DIRS "")
set(PCAP_CONFIG_STATIC_LIBRARIES "")
foreach(_arg IN LISTS LIBS_LIST)
if(_arg MATCHES "^-L")
# Add this directory to the library directory hints.
string(REGEX REPLACE "^-L" "" _dir ${_arg})
list(APPEND PCAP_CONFIG_STATIC_LIBRARY_DIRS ${_dir})
elseif(_arg MATCHES "^-l")
# Add this library to the requirements for static linking.
string(REGEX REPLACE "^-l" "" _lib ${_arg})
list(APPEND PCAP_CONFIG_STATIC_LIBRARIES ${_lib})
endif()
endforeach()
endif()
endif()
endif()
#
# Locate the actual include directory. For pkg-config the
# PC_PCAP_INCLUDE_DIRS variable could be empty if the default
# header search path is sufficient to locate the header file.
# For macOS, the directory returned by pcap-config is wrong, so
# this will make sure to find a valid path.
#
find_path(PCAP_INCLUDE_DIR
NAMES
pcap/pcap.h
pcap.h
HINTS
${PC_PCAP_INCLUDE_DIRS}
${PCAP_CONFIG_INCLUDE_DIRS}
"${PCAP_HINTS}/Include"
)
# On Windows we load wpcap.dll explicitly and probe its functions in
# caputils\capture-wpcap.c. We don't want to link with pcap.lib since
# that would bring in the non-capturing (null) pcap.dll from the vcpkg
# library.
if(WIN32)
set(_pkg_required_vars PCAP_INCLUDE_DIR)
else()
find_library(PCAP_LIBRARY
NAMES
pcap
HINTS
${PC_PCAP_LIBRARY_DIRS}
${PCAP_CONFIG_LIBRARY_DIRS}
)
set(_pkg_required_vars PCAP_LIBRARY PCAP_INCLUDE_DIR)
endif()
if(UNIX AND CMAKE_FIND_LIBRARY_SUFFIXES STREQUAL ".a")
# Try to find the static library (XXX - what about AIX?)
if(PC_PCAP_FOUND)
set(_pcap_static_libraries ${PC_PCAP_STATIC_LIBRARIES})
elseif(PCAP_CONFIG)
set(_pcap_static_libraries ${PCAP_CONFIG_STATIC_LIBRARIES})
else()
#
# No pkg-config nor pcap-config found, hope that this single library is
# sufficient for static linking.
#
set(_pcap_static_libraries pcap)
endif()
set(PCAP_STATIC_LIBRARIES "")
foreach(_lib IN LISTS _pcap_static_libraries)
#
# Try to find that library, so we get its full path, as
# we do with dynamic libraries.
#
string(MAKE_C_IDENTIFIER "PCAP_STATIC_${_lib}_LIBRARY" _libvar)
find_library(${_libvar} ${_lib}
HINTS
${PC_PCAP_STATIC_LIBRARY_DIRS}
${PCAP_CONFIG_STATIC_LIBRARY_DIRS}
)
set(_libpath ${${_libvar}})
if(_libpath)
list(APPEND PCAP_STATIC_LIBRARIES ${_libpath})
endif()
endforeach()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PCAP DEFAULT_MSG ${_pkg_required_vars})
mark_as_advanced(${_pkg_required_vars})
if(PCAP_FOUND)
set(PCAP_INCLUDE_DIRS ${PCAP_INCLUDE_DIR})
if(UNIX AND CMAKE_FIND_LIBRARY_SUFFIXES STREQUAL ".a")
# Link with static libpcap and its transitive dependencies.
set(PCAP_LIBRARIES ${PCAP_STATIC_LIBRARIES})
else()
set(PCAP_LIBRARIES ${PCAP_LIBRARY})
endif()
#Functions
include( CMakePushCheckState )
include( CheckFunctionExists )
include( CheckVariableExists )
cmake_push_check_state()
set( CMAKE_REQUIRED_INCLUDES ${PCAP_INCLUDE_DIRS} )
set( CMAKE_REQUIRED_LIBRARIES ${PCAP_LIBRARIES} )
include(CheckSymbolExists)
if(WIN32)
#
# Prepopulate some values. WinPcap 3.1 and later, and Npcap, have these
# in their SDK, and compilation checks on Windows can be slow. We check
# whether they're present at run time, when we load wpcap.dll, and work
# around their absence or report an error.
#
set(HAVE_PCAP_FREECODE TRUE)
set(HAVE_PCAP_CREATE TRUE)
set(HAVE_PCAP_FREE_DATALINKS TRUE)
set(HAVE_PCAP_OPEN TRUE)
set(HAVE_PCAP_SETSAMPLING TRUE)
set(HAVE_PCAP_SET_TSTAMP_PRECISION TRUE)
set(HAVE_PCAP_SET_TSTAMP_TYPE TRUE)
else(WIN32)
#
# Make sure we have at least libpcap 0.8, because we we require at
# least libpcap 0.8's APIs.
#
# We check whether pcap_lib_version is defined in the pcap header,
# using it as a proxy for all the 0.8 API's. if not, we fail.
#
check_symbol_exists( pcap_lib_version ${PCAP_INCLUDE_DIR}/pcap.h HAVE_PCAP_LIB_VERSION )
if( NOT HAVE_PCAP_LIB_VERSION )
message(FATAL_ERROR "You need libpcap 0.8 or later")
endif( NOT HAVE_PCAP_LIB_VERSION )
check_function_exists( "pcap_freecode" HAVE_PCAP_FREECODE )
check_function_exists( "pcap_create" HAVE_PCAP_CREATE )
check_function_exists( "pcap_free_datalinks" HAVE_PCAP_FREE_DATALINKS )
check_function_exists( "pcap_open" HAVE_PCAP_OPEN )
if( HAVE_PCAP_OPEN )
#
# XXX - this *should* be checked for independently of checking
# for pcap_open(), as you might have pcap_setsampling() without
# remote capture support.
#
# However, 1) the sampling options are treated as remote options
# in the GUI and and 2) having pcap_setsampling() doesn't mean
# you have sampling support. libpcap needs a way to indicate
# whether a given device supports sampling, and the GUI should
# be changed to decouple them.
#
# (Actually, libpcap needs a general mechanism to offer options
# for particular devices, and Wireshark needs to use that
# mechanism. The former is a work in progress.)
#
# (Note: another work in progress is support for remote
# capturing using pcap_create()/pcap_activate(), which we
# also need to support once it's available.)
#
check_function_exists( "pcap_setsampling" HAVE_PCAP_SETSAMPLING )
endif( HAVE_PCAP_OPEN )
endif(WIN32)
if( HAVE_PCAP_CREATE )
#
# If we have pcap_create(), we have pcap_set_buffer_size(), and
# can set the capture buffer size.
#
# Otherwise, if this is Windows, we have pcap_setbuff(), and can
# set the capture buffer size.
#
set( CAN_SET_CAPTURE_BUFFER_SIZE TRUE )
endif()
check_function_exists( "pcap_set_tstamp_precision" HAVE_PCAP_SET_TSTAMP_PRECISION )
check_function_exists( "pcap_set_tstamp_type" HAVE_PCAP_SET_TSTAMP_TYPE )
# Remote pcap checks
if( HAVE_PCAP_OPEN )
set( HAVE_PCAP_REMOTE 1 )
endif()
cmake_pop_check_state()
endif()
if(PCAP_FOUND AND NOT TARGET pcap::pcap)
if(WIN32)
add_library(pcap::pcap INTERFACE IMPORTED)
set_target_properties(pcap::pcap PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${PCAP_INCLUDE_DIRS}"
)
else()
add_library(pcap::pcap UNKNOWN IMPORTED)
set_target_properties(pcap::pcap PROPERTIES
IMPORTED_LOCATION "${PCAP_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${PCAP_INCLUDE_DIRS}"
)
endif()
endif()

View File

@@ -1,53 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H 1
/* macros shared across multiple executables */
#define DEFAULT_CHUSER "nobody"
#define COLLECTOR_UNIX_SOCKET "/tmp/ndpid-collector.sock"
#define DISTRIBUTOR_UNIX_SOCKET "/tmp/ndpid-distributor.sock"
#define DISTRIBUTOR_HOST "127.0.0.1"
#define DISTRIBUTOR_PORT 7000u
/*
* NOTE: Buffer size needs to keep in sync with other implementations
* e.g. dependencies/nDPIsrvd.py
*/
#define NETWORK_BUFFER_MAX_SIZE 33792u /* 8192 + 8192 + 8192 + 8192 + 1024 */
#define NETWORK_BUFFER_LENGTH_DIGITS 5u
#define NETWORK_BUFFER_LENGTH_DIGITS_STR "5"
#define TIME_S_TO_US(s) (s * 1000llu * 1000llu)
/* nDPId default config options */
#define nDPId_PIDFILE "/tmp/ndpid.pid"
#define nDPId_MAX_FLOWS_PER_THREAD 4096u
#define nDPId_MAX_IDLE_FLOWS_PER_THREAD (nDPId_MAX_FLOWS_PER_THREAD / 32u)
#define nDPId_MAX_READER_THREADS 32u
#define nDPId_ERROR_EVENT_THRESHOLD_N 16u
#define nDPId_ERROR_EVENT_THRESHOLD_TIME TIME_S_TO_US(10u) /* 10 sec */
#define nDPId_DAEMON_STATUS_INTERVAL TIME_S_TO_US(600u) /* 600 sec */
#define nDPId_MEMORY_PROFILING_LOG_INTERVAL TIME_S_TO_US(5u) /* 5 sec */
#define nDPId_COMPRESSION_SCAN_INTERVAL TIME_S_TO_US(20u) /* 20 sec */
#define nDPId_COMPRESSION_FLOW_INACTIVITY TIME_S_TO_US(30u) /* 30 sec */
#define nDPId_FLOW_SCAN_INTERVAL TIME_S_TO_US(10u) /* 10 sec */
#define nDPId_GENERIC_IDLE_TIME TIME_S_TO_US(600u) /* 600 sec */
#define nDPId_ICMP_IDLE_TIME TIME_S_TO_US(120u) /* 120 sec */
#define nDPId_TCP_IDLE_TIME TIME_S_TO_US(7440u) /* 7440 sec */
#define nDPId_UDP_IDLE_TIME TIME_S_TO_US(180u) /* 180 sec */
#define nDPId_TCP_POST_END_FLOW_TIME TIME_S_TO_US(120u) /* 120 sec */
#define nDPId_THREAD_DISTRIBUTION_SEED 0x03dd018b
#define nDPId_PACKETS_PLEN_MAX 8192u /* 8kB */
#define nDPId_PACKETS_PER_FLOW_TO_SEND 15u
#define nDPId_PACKETS_PER_FLOW_TO_PROCESS NDPI_DEFAULT_MAX_NUM_PKTS_PER_FLOW_TO_DISSECT
#define nDPId_PACKETS_PER_FLOW_TO_ANALYZE 32u
#define nDPId_ANALYZE_PLEN_MAX 1504u
#define nDPId_ANALYZE_PLEN_BIN_LEN 32u
#define nDPId_ANALYZE_PLEN_NUM_BINS 48u
#define nDPId_FLOW_STRUCT_SEED 0x5defc104
/* nDPIsrvd default config options */
#define nDPIsrvd_PIDFILE "/tmp/ndpisrvd.pid"
#define nDPIsrvd_MAX_REMOTE_DESCRIPTORS 128
#define nDPIsrvd_MAX_WRITE_BUFFERS 1024
#endif

View File

@@ -1,90 +0,0 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IndentCaseLabels: false
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
...

View File

@@ -1,4 +0,0 @@
language: c
sudo: false
script:
- make test

View File

@@ -1,20 +0,0 @@
Copyright (c) 2010 Serge A. Zaitsev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,36 +0,0 @@
# You can put your build options here
-include config.mk
test: test_default test_strict test_links test_strict_links
test_default: test/tests.c jsmn.h
$(CC) $(CFLAGS) $(LDFLAGS) $< -o test/$@
./test/$@
test_strict: test/tests.c jsmn.h
$(CC) -DJSMN_STRICT=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@
./test/$@
test_links: test/tests.c jsmn.h
$(CC) -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@
./test/$@
test_strict_links: test/tests.c jsmn.h
$(CC) -DJSMN_STRICT=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@
./test/$@
simple_example: example/simple.c jsmn.h
$(CC) $(LDFLAGS) $< -o $@
jsondump: example/jsondump.c jsmn.h
$(CC) $(LDFLAGS) $< -o $@
fmt:
clang-format -i jsmn.h test/*.[ch] example/*.[ch]
lint:
clang-tidy jsmn.h --checks='*'
clean:
rm -f *.o example/*.o
rm -f simple_example
rm -f jsondump
.PHONY: clean test

View File

@@ -1,182 +0,0 @@
JSMN
====
[![Build Status](https://travis-ci.org/zserge/jsmn.svg?branch=master)](https://travis-ci.org/zserge/jsmn)
jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be
easily integrated into resource-limited or embedded projects.
You can find more information about JSON format at [json.org][1]
Library sources are available at https://github.com/zserge/jsmn
The web page with some information about jsmn can be found at
[http://zserge.com/jsmn.html][2]
Philosophy
----------
Most JSON parsers offer you a bunch of functions to load JSON data, parse it
and extract any value by its name. jsmn proves that checking the correctness of
every JSON packet or allocating temporary objects to store parsed JSON fields
often is an overkill.
JSON format itself is extremely simple, so why should we complicate it?
jsmn is designed to be **robust** (it should work fine even with erroneous
data), **fast** (it should parse data on the fly), **portable** (no superfluous
dependencies or non-standard C extensions). And of course, **simplicity** is a
key feature - simple code style, simple algorithm, simple integration into
other projects.
Features
--------
* compatible with C89
* no dependencies (even libc!)
* highly portable (tested on x86/amd64, ARM, AVR)
* about 200 lines of code
* extremely small code footprint
* API contains only 2 functions
* no dynamic memory allocation
* incremental single-pass parsing
* library code is covered with unit-tests
Design
------
The rudimentary jsmn object is a **token**. Let's consider a JSON string:
'{ "name" : "Jack", "age" : 27 }'
It holds the following tokens:
* Object: `{ "name" : "Jack", "age" : 27}` (the whole object)
* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values)
* Number: `27`
In jsmn, tokens do not hold any data, but point to token boundaries in JSON
string instead. In the example above jsmn will create tokens like: Object
[0..31], String [3..7], String [12..16], String [20..23], Number [27..29].
Every jsmn token has a type, which indicates the type of corresponding JSON
token. jsmn supports the following token types:
* Object - a container of key-value pairs, e.g.:
`{ "foo":"bar", "x":0.3 }`
* Array - a sequence of values, e.g.:
`[ 1, 2, 3 ]`
* String - a quoted sequence of chars, e.g.: `"foo"`
* Primitive - a number, a boolean (`true`, `false`) or `null`
Besides start/end positions, jsmn tokens for complex types (like arrays
or objects) also contain a number of child items, so you can easily follow
object hierarchy.
This approach provides enough information for parsing any JSON data and makes
it possible to use zero-copy techniques.
Usage
-----
Download `jsmn.h`, include it, done.
```
#include "jsmn.h"
...
jsmn_parser p;
jsmntok_t t[128]; /* We expect no more than 128 JSON tokens */
jsmn_init(&p);
r = jsmn_parse(&p, s, strlen(s), t, 128); // "s" is the char array holding the json content
```
Since jsmn is a single-header, header-only library, for more complex use cases
you might need to define additional macros. `#define JSMN_STATIC` hides all
jsmn API symbols by making them static. Also, if you want to include `jsmn.h`
from multiple C files, to avoid duplication of symbols you may define `JSMN_HEADER` macro.
```
/* In every .c file that uses jsmn include only declarations: */
#define JSMN_HEADER
#include "jsmn.h"
/* Additionally, create one jsmn.c file for jsmn implementation: */
#include "jsmn.h"
```
API
---
Token types are described by `jsmntype_t`:
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1 << 0,
JSMN_ARRAY = 1 << 1,
JSMN_STRING = 1 << 2,
JSMN_PRIMITIVE = 1 << 3
} jsmntype_t;
**Note:** Unlike JSON data types, primitive tokens are not divided into
numbers, booleans and null, because one can easily tell the type using the
first character:
* <code>'t', 'f'</code> - boolean
* <code>'n'</code> - null
* <code>'-', '0'..'9'</code> - number
Token is an object of `jsmntok_t` type:
typedef struct {
jsmntype_t type; // Token type
int start; // Token start position
int end; // Token end position
int size; // Number of child (nested) tokens
} jsmntok_t;
**Note:** string tokens point to the first character after
the opening quote and the previous symbol before final quote. This was made
to simplify string extraction from JSON data.
All job is done by `jsmn_parser` object. You can initialize a new parser using:
jsmn_parser parser;
jsmntok_t tokens[10];
jsmn_init(&parser);
// js - pointer to JSON string
// tokens - an array of tokens available
// 10 - number of tokens available
jsmn_parse(&parser, js, strlen(js), tokens, 10);
This will create a parser, and then it tries to parse up to 10 JSON tokens from
the `js` string.
A non-negative return value of `jsmn_parse` is the number of tokens actually
used by the parser.
Passing NULL instead of the tokens array would not store parsing results, but
instead the function will return the number of tokens needed to parse the given
string. This can be useful if you don't know yet how many tokens to allocate.
If something goes wrong, you will get an error. Error will be one of these:
* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted
* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large
* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data
If you get `JSMN_ERROR_NOMEM`, you can re-allocate more tokens and call
`jsmn_parse` once more. If you read json data from the stream, you can
periodically call `jsmn_parse` and check if return value is `JSMN_ERROR_PART`.
You will get this error until you reach the end of JSON data.
Other info
----------
This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php),
so feel free to integrate it in your commercial products.
[1]: http://www.json.org/
[2]: http://zserge.com/jsmn.html

View File

@@ -1,134 +0,0 @@
#include "../jsmn.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Function realloc_it() is a wrapper function for standard realloc()
* with one difference - it frees old memory pointer in case of realloc
* failure. Thus, DO NOT use old data pointer in anyway after call to
* realloc_it(). If your code has some kind of fallback algorithm if
* memory can't be re-allocated - use standard realloc() instead.
*/
static inline void *realloc_it(void *ptrmem, size_t size) {
void *p = realloc(ptrmem, size);
if (!p) {
free(ptrmem);
fprintf(stderr, "realloc(): errno=%d\n", errno);
}
return p;
}
/*
* An example of reading JSON from stdin and printing its content to stdout.
* The output looks like YAML, but I'm not sure if it's really compatible.
*/
static int dump(const char *js, jsmntok_t *t, size_t count, int indent) {
int i, j, k;
jsmntok_t *key;
if (count == 0) {
return 0;
}
if (t->type == JSMN_PRIMITIVE) {
printf("%.*s", t->end - t->start, js + t->start);
return 1;
} else if (t->type == JSMN_STRING) {
printf("'%.*s'", t->end - t->start, js + t->start);
return 1;
} else if (t->type == JSMN_OBJECT) {
printf("\n");
j = 0;
for (i = 0; i < t->size; i++) {
for (k = 0; k < indent; k++) {
printf(" ");
}
key = t + 1 + j;
j += dump(js, key, count - j, indent + 1);
if (key->size > 0) {
printf(": ");
j += dump(js, t + 1 + j, count - j, indent + 1);
}
printf("\n");
}
return j + 1;
} else if (t->type == JSMN_ARRAY) {
j = 0;
printf("\n");
for (i = 0; i < t->size; i++) {
for (k = 0; k < indent - 1; k++) {
printf(" ");
}
printf(" - ");
j += dump(js, t + 1 + j, count - j, indent + 1);
printf("\n");
}
return j + 1;
}
return 0;
}
int main() {
int r;
int eof_expected = 0;
char *js = NULL;
size_t jslen = 0;
char buf[BUFSIZ];
jsmn_parser p;
jsmntok_t *tok;
size_t tokcount = 2;
/* Prepare parser */
jsmn_init(&p);
/* Allocate some tokens as a start */
tok = malloc(sizeof(*tok) * tokcount);
if (tok == NULL) {
fprintf(stderr, "malloc(): errno=%d\n", errno);
return 3;
}
for (;;) {
/* Read another chunk */
r = fread(buf, 1, sizeof(buf), stdin);
if (r < 0) {
fprintf(stderr, "fread(): %d, errno=%d\n", r, errno);
return 1;
}
if (r == 0) {
if (eof_expected != 0) {
return 0;
} else {
fprintf(stderr, "fread(): unexpected EOF\n");
return 2;
}
}
js = realloc_it(js, jslen + r + 1);
if (js == NULL) {
return 3;
}
strncpy(js + jslen, buf, r);
jslen = jslen + r;
again:
r = jsmn_parse(&p, js, jslen, tok, tokcount);
if (r < 0) {
if (r == JSMN_ERROR_NOMEM) {
tokcount = tokcount * 2;
tok = realloc_it(tok, sizeof(*tok) * tokcount);
if (tok == NULL) {
return 3;
}
goto again;
}
} else {
dump(js, tok, p.toknext, 0);
eof_expected = 1;
}
}
return EXIT_SUCCESS;
}

View File

@@ -1,77 +0,0 @@
#include "../jsmn.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* A small example of jsmn parsing when JSON structure is known and number of
* tokens is predictable.
*/
static const char *JSON_STRING =
"{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n "
"\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
return 0;
}
return -1;
}
int main() {
int i;
int r;
jsmn_parser p;
jsmntok_t t[128]; /* We expect no more than 128 tokens */
jsmn_init(&p);
r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t,
sizeof(t) / sizeof(t[0]));
if (r < 0) {
printf("Failed to parse JSON: %d\n", r);
return 1;
}
/* Assume the top-level element is an object */
if (r < 1 || t[0].type != JSMN_OBJECT) {
printf("Object expected\n");
return 1;
}
/* Loop over all keys of the root object */
for (i = 1; i < r; i++) {
if (jsoneq(JSON_STRING, &t[i], "user") == 0) {
/* We may use strndup() to fetch string value */
printf("- User: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
} else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) {
/* We may additionally check if the value is either "true" or "false" */
printf("- Admin: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
} else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) {
/* We may want to do strtol() here to get numeric value */
printf("- UID: %.*s\n", t[i + 1].end - t[i + 1].start,
JSON_STRING + t[i + 1].start);
i++;
} else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) {
int j;
printf("- Groups:\n");
if (t[i + 1].type != JSMN_ARRAY) {
continue; /* We expect groups to be an array of strings */
}
for (j = 0; j < t[i + 1].size; j++) {
jsmntok_t *g = &t[i + j + 2];
printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start);
}
i += t[i + 1].size + 1;
} else {
printf("Unexpected key: %.*s\n", t[i].end - t[i].start,
JSON_STRING + t[i].start);
}
}
return EXIT_SUCCESS;
}

View File

@@ -1,471 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2010 Serge Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef JSMN_H
#define JSMN_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef JSMN_STATIC
#define JSMN_API static
#else
#define JSMN_API extern
#endif
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1 << 0,
JSMN_ARRAY = 1 << 1,
JSMN_STRING = 1 << 2,
JSMN_PRIMITIVE = 1 << 3
} jsmntype_t;
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
/**
* JSON token description.
* type type (object, array, string etc.)
* start start position in JSON data string
* end end position in JSON data string
*/
typedef struct jsmntok {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string.
*/
typedef struct jsmn_parser {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g. parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
JSMN_API void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each
* describing
* a single JSON object.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens);
#ifndef JSMN_HEADER
/**
* Allocates a fresh unused token from the token pool.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *tok;
if (parser->toknext >= num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef JSMN_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
const int start, const int end) {
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start;
start = parser->pos;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t':
case '\r':
case '\n':
case ' ':
case ',':
case ']':
case '}':
goto found;
default:
/* to quiet a warning from gcc*/
break;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
if (tokens == NULL) {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return 0;
}
/**
* Fills next token with JSON string.
*/
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
const size_t len, jsmntok_t *tokens,
const size_t num_tokens) {
jsmntok_t *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
if (tokens == NULL) {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return 0;
}
/* Backslash: Quoted symbol expected */
if (c == '\\' && parser->pos + 1 < len) {
int i;
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"':
case '/':
case '\\':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
break;
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
i++) {
/* If it isn't a hex character we have an error */
if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
parser->pos = start;
return JSMN_ERROR_INVAL;
}
parser->pos++;
}
parser->pos--;
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
/**
* Parse JSON string and fill tokens.
*/
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
jsmntok_t *tokens, const unsigned int num_tokens) {
int r;
int i;
jsmntok_t *token;
int count = parser->toknext;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{':
case '[':
count++;
if (tokens == NULL) {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
return JSMN_ERROR_NOMEM;
}
if (parser->toksuper != -1) {
jsmntok_t *t = &tokens[parser->toksuper];
#ifdef JSMN_STRICT
/* In strict mode an object or array can't become a key */
if (t->type == JSMN_OBJECT) {
return JSMN_ERROR_INVAL;
}
#endif
t->size++;
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}':
case ']':
if (tokens == NULL) {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
if (parser->toknext < 1) {
return JSMN_ERROR_INVAL;
}
token = &tokens[parser->toknext - 1];
for (;;) {
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) {
if (token->type != type || parser->toksuper == -1) {
return JSMN_ERROR_INVAL;
}
break;
}
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) {
return JSMN_ERROR_INVAL;
}
for (; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
case '\t':
case '\r':
case '\n':
case ' ':
break;
case ':':
parser->toksuper = parser->toknext - 1;
break;
case ',':
if (tokens != NULL && parser->toksuper != -1 &&
tokens[parser->toksuper].type != JSMN_ARRAY &&
tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
parser->toksuper = tokens[parser->toksuper].parent;
#else
for (i = parser->toknext - 1; i >= 0; i--) {
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
if (tokens[i].start != -1 && tokens[i].end == -1) {
parser->toksuper = i;
break;
}
}
}
#endif
}
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 't':
case 'f':
case 'n':
/* And they must not be keys of the object */
if (tokens != NULL && parser->toksuper != -1) {
const jsmntok_t *t = &tokens[parser->toksuper];
if (t->type == JSMN_OBJECT ||
(t->type == JSMN_STRING && t->size != 0)) {
return JSMN_ERROR_INVAL;
}
}
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
if (tokens != NULL) {
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) {
return JSMN_ERROR_PART;
}
}
}
return count;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
JSMN_API void jsmn_init(jsmn_parser *parser) {
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}
#endif /* JSMN_HEADER */
#ifdef __cplusplus
}
#endif
#endif /* JSMN_H */

View File

@@ -1,16 +0,0 @@
{
"name": "jsmn",
"keywords": "json",
"description": "Minimalistic JSON parser/tokenizer in C. It can be easily integrated into resource-limited or embedded projects",
"repository":
{
"type": "git",
"url": "https://github.com/zserge/jsmn.git"
},
"frameworks": "*",
"platforms": "*",
"examples": [
"example/*.c"
],
"exclude": "test"
}

View File

@@ -1,31 +0,0 @@
#ifndef __TEST_H__
#define __TEST_H__
static int test_passed = 0;
static int test_failed = 0;
/* Terminate current test with error */
#define fail() return __LINE__
/* Successful end of the test case */
#define done() return 0
/* Check single condition */
#define check(cond) \
do { \
if (!(cond)) \
fail(); \
} while (0)
/* Test runner */
static void test(int (*func)(void), const char *name) {
int r = func();
if (r == 0) {
test_passed++;
} else {
test_failed++;
printf("FAILED: %s (at line %d)\n", name, r);
}
}
#endif /* __TEST_H__ */

View File

@@ -1,359 +0,0 @@
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test.h"
#include "testutil.h"
int test_empty(void) {
check(parse("{}", 1, 1, JSMN_OBJECT, 0, 2, 0));
check(parse("[]", 1, 1, JSMN_ARRAY, 0, 2, 0));
check(parse("[{},{}]", 3, 3, JSMN_ARRAY, 0, 7, 2, JSMN_OBJECT, 1, 3, 0,
JSMN_OBJECT, 4, 6, 0));
return 0;
}
int test_object(void) {
check(parse("{\"a\":0}", 3, 3, JSMN_OBJECT, 0, 7, 1, JSMN_STRING, "a", 1,
JSMN_PRIMITIVE, "0"));
check(parse("{\"a\":[]}", 3, 3, JSMN_OBJECT, 0, 8, 1, JSMN_STRING, "a", 1,
JSMN_ARRAY, 5, 7, 0));
check(parse("{\"a\":{},\"b\":{}}", 5, 5, JSMN_OBJECT, -1, -1, 2, JSMN_STRING,
"a", 1, JSMN_OBJECT, -1, -1, 0, JSMN_STRING, "b", 1, JSMN_OBJECT,
-1, -1, 0));
check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7,
JSMN_OBJECT, -1, -1, 3, JSMN_STRING, "Day", 1, JSMN_PRIMITIVE,
"26", JSMN_STRING, "Month", 1, JSMN_PRIMITIVE, "9", JSMN_STRING,
"Year", 1, JSMN_PRIMITIVE, "12"));
check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, JSMN_OBJECT, -1, -1, 2,
JSMN_STRING, "a", 1, JSMN_PRIMITIVE, "0", JSMN_STRING, "b", 1,
JSMN_STRING, "c", 0));
#ifdef JSMN_STRICT
check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3));
check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3));
check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3));
check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3));
check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5));
/* FIXME */
/*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/
/*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/
/*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/
/*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/
/*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/
/*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/
#endif
return 0;
}
int test_array(void) {
/* FIXME */
/*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/
/*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/
check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10"));
check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3));
/* FIXME */
/*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/
return 0;
}
int test_primitive(void) {
check(parse("{\"boolVar\" : true }", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "boolVar", 1, JSMN_PRIMITIVE, "true"));
check(parse("{\"boolVar\" : false }", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "boolVar", 1, JSMN_PRIMITIVE, "false"));
check(parse("{\"nullVar\" : null }", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "nullVar", 1, JSMN_PRIMITIVE, "null"));
check(parse("{\"intVar\" : 12}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING,
"intVar", 1, JSMN_PRIMITIVE, "12"));
check(parse("{\"floatVar\" : 12.345}", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.345"));
return 0;
}
int test_string(void) {
check(parse("{\"strVar\" : \"hello world\"}", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "strVar", 1, JSMN_STRING, "hello world", 0));
check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3,
JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "strVar", 1, JSMN_STRING,
"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0));
check(parse("{\"strVar\": \"\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING,
"strVar", 1, JSMN_STRING, "", 0));
check(parse("{\"a\":\"\\uAbcD\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING,
"a", 1, JSMN_STRING, "\\uAbcD", 0));
check(parse("{\"a\":\"str\\u0000\"}", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "a", 1, JSMN_STRING, "str\\u0000", 0));
check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "a", 1, JSMN_STRING, "\\uFFFFstr", 0));
check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, JSMN_OBJECT, -1, -1, 1,
JSMN_STRING, "a", 1, JSMN_ARRAY, -1, -1, 1, JSMN_STRING,
"\\u0280", 0));
check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3));
check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3));
check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4));
return 0;
}
int test_partial_string(void) {
int r;
unsigned long i;
jsmn_parser p;
jsmntok_t tok[5];
const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}";
jsmn_init(&p);
for (i = 1; i <= strlen(js); i++) {
r = jsmn_parse(&p, js, i, tok, sizeof(tok) / sizeof(tok[0]));
if (i == strlen(js)) {
check(r == 5);
check(tokeq(js, tok, 5, JSMN_OBJECT, -1, -1, 2, JSMN_STRING, "x", 1,
JSMN_STRING, "va\\\\ue", 0, JSMN_STRING, "y", 1, JSMN_STRING,
"value y", 0));
} else {
check(r == JSMN_ERROR_PART);
}
}
return 0;
}
int test_partial_array(void) {
#ifdef JSMN_STRICT
int r;
unsigned long i;
jsmn_parser p;
jsmntok_t tok[10];
const char *js = "[ 1, true, [123, \"hello\"]]";
jsmn_init(&p);
for (i = 1; i <= strlen(js); i++) {
r = jsmn_parse(&p, js, i, tok, sizeof(tok) / sizeof(tok[0]));
if (i == strlen(js)) {
check(r == 6);
check(tokeq(js, tok, 6, JSMN_ARRAY, -1, -1, 3, JSMN_PRIMITIVE, "1",
JSMN_PRIMITIVE, "true", JSMN_ARRAY, -1, -1, 2, JSMN_PRIMITIVE,
"123", JSMN_STRING, "hello", 0));
} else {
check(r == JSMN_ERROR_PART);
}
}
#endif
return 0;
}
int test_array_nomem(void) {
int i;
int r;
jsmn_parser p;
jsmntok_t toksmall[10], toklarge[10];
const char *js;
js = " [ 1, true, [123, \"hello\"]]";
for (i = 0; i < 6; i++) {
jsmn_init(&p);
memset(toksmall, 0, sizeof(toksmall));
memset(toklarge, 0, sizeof(toklarge));
r = jsmn_parse(&p, js, strlen(js), toksmall, i);
check(r == JSMN_ERROR_NOMEM);
memcpy(toklarge, toksmall, sizeof(toksmall));
r = jsmn_parse(&p, js, strlen(js), toklarge, 10);
check(r >= 0);
check(tokeq(js, toklarge, 4, JSMN_ARRAY, -1, -1, 3, JSMN_PRIMITIVE, "1",
JSMN_PRIMITIVE, "true", JSMN_ARRAY, -1, -1, 2, JSMN_PRIMITIVE,
"123", JSMN_STRING, "hello", 0));
}
return 0;
}
int test_unquoted_keys(void) {
#ifndef JSMN_STRICT
int r;
jsmn_parser p;
jsmntok_t tok[10];
const char *js;
jsmn_init(&p);
js = "key1: \"value\"\nkey2 : 123";
r = jsmn_parse(&p, js, strlen(js), tok, 10);
check(r >= 0);
check(tokeq(js, tok, 4, JSMN_PRIMITIVE, "key1", JSMN_STRING, "value", 0,
JSMN_PRIMITIVE, "key2", JSMN_PRIMITIVE, "123"));
#endif
return 0;
}
int test_issue_22(void) {
int r;
jsmn_parser p;
jsmntok_t tokens[128];
const char *js;
js =
"{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, "
"\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", "
"\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], "
"\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, "
"\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", "
"\"imageheight\":64, \"imagewidth\":160, \"margin\":0, "
"\"name\":\"Tiles\", "
"\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 "
"}], "
"\"tilewidth\":32, \"version\":1, \"width\":10 }";
jsmn_init(&p);
r = jsmn_parse(&p, js, strlen(js), tokens, 128);
check(r >= 0);
return 0;
}
int test_issue_27(void) {
const char *js =
"{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", ";
check(parse(js, JSMN_ERROR_PART, 8));
return 0;
}
int test_input_length(void) {
const char *js;
int r;
jsmn_parser p;
jsmntok_t tokens[10];
js = "{\"a\": 0}garbage";
jsmn_init(&p);
r = jsmn_parse(&p, js, 8, tokens, 10);
check(r == 3);
check(tokeq(js, tokens, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "a", 1,
JSMN_PRIMITIVE, "0"));
return 0;
}
int test_count(void) {
jsmn_parser p;
const char *js;
js = "{}";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1);
js = "[]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1);
js = "[[]]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2);
js = "[[], []]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3);
js = "[[], []]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3);
js = "[[], [[]], [[], []]]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7);
js = "[\"a\", [[], []]]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5);
js = "[[], \"[], [[]]\", [[]]]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5);
js = "[1, 2, 3]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4);
js = "[1, 2, [3, \"a\"], null]";
jsmn_init(&p);
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7);
return 0;
}
int test_nonstrict(void) {
#ifndef JSMN_STRICT
const char *js;
js = "a: 0garbage";
check(parse(js, 2, 2, JSMN_PRIMITIVE, "a", JSMN_PRIMITIVE, "0garbage"));
js = "Day : 26\nMonth : Sep\n\nYear: 12";
check(parse(js, 6, 6, JSMN_PRIMITIVE, "Day", JSMN_PRIMITIVE, "26",
JSMN_PRIMITIVE, "Month", JSMN_PRIMITIVE, "Sep", JSMN_PRIMITIVE,
"Year", JSMN_PRIMITIVE, "12"));
/* nested {s don't cause a parse error. */
js = "\"key {1\": 1234";
check(parse(js, 2, 2, JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234"));
#endif
return 0;
}
int test_unmatched_brackets(void) {
const char *js;
js = "\"key 1\": 1234}";
check(parse(js, JSMN_ERROR_INVAL, 2));
js = "{\"key 1\": 1234";
check(parse(js, JSMN_ERROR_PART, 3));
js = "{\"key 1\": 1234}}";
check(parse(js, JSMN_ERROR_INVAL, 3));
js = "\"key 1\"}: 1234";
check(parse(js, JSMN_ERROR_INVAL, 3));
js = "{\"key {1\": 1234}";
check(parse(js, 3, 3, JSMN_OBJECT, 0, 16, 1, JSMN_STRING, "key {1", 1,
JSMN_PRIMITIVE, "1234"));
js = "{\"key 1\":{\"key 2\": 1234}";
check(parse(js, JSMN_ERROR_PART, 5));
return 0;
}
int test_object_key(void) {
const char *js;
js = "{\"key\": 1}";
check(parse(js, 3, 3, JSMN_OBJECT, 0, 10, 1, JSMN_STRING, "key", 1,
JSMN_PRIMITIVE, "1"));
#ifdef JSMN_STRICT
js = "{true: 1}";
check(parse(js, JSMN_ERROR_INVAL, 3));
js = "{1: 1}";
check(parse(js, JSMN_ERROR_INVAL, 3));
js = "{{\"key\": 1}: 2}";
check(parse(js, JSMN_ERROR_INVAL, 5));
js = "{[1,2]: 2}";
check(parse(js, JSMN_ERROR_INVAL, 5));
#endif
return 0;
}
int main(void) {
test(test_empty, "test for a empty JSON objects/arrays");
test(test_object, "test for a JSON objects");
test(test_array, "test for a JSON arrays");
test(test_primitive, "test primitive JSON data types");
test(test_string, "test string JSON data types");
test(test_partial_string, "test partial JSON string parsing");
test(test_partial_array, "test partial array reading");
test(test_array_nomem, "test array reading with a smaller number of tokens");
test(test_unquoted_keys, "test unquoted keys (like in JavaScript)");
test(test_input_length, "test strings that are not null-terminated");
test(test_issue_22, "test issue #22");
test(test_issue_27, "test issue #27");
test(test_count, "test tokens count estimation");
test(test_nonstrict, "test for non-strict mode");
test(test_unmatched_brackets, "test for unmatched brackets");
test(test_object_key, "test for key type");
printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed);
return (test_failed > 0);
}

View File

@@ -1,96 +0,0 @@
#ifndef __TEST_UTIL_H__
#define __TEST_UTIL_H__
#include "../jsmn.h"
static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok,
va_list ap) {
if (numtok > 0) {
unsigned long i;
int start, end, size;
jsmntype_t type;
char *value;
size = -1;
value = NULL;
for (i = 0; i < numtok; i++) {
type = va_arg(ap, jsmntype_t);
if (type == JSMN_STRING) {
value = va_arg(ap, char *);
size = va_arg(ap, int);
start = end = -1;
} else if (type == JSMN_PRIMITIVE) {
value = va_arg(ap, char *);
start = end = size = -1;
} else {
start = va_arg(ap, int);
end = va_arg(ap, int);
size = va_arg(ap, int);
value = NULL;
}
if (t[i].type != type) {
printf("token %lu type is %d, not %d\n", i, t[i].type, type);
return 0;
}
if (start != -1 && end != -1) {
if (t[i].start != start) {
printf("token %lu start is %d, not %d\n", i, t[i].start, start);
return 0;
}
if (t[i].end != end) {
printf("token %lu end is %d, not %d\n", i, t[i].end, end);
return 0;
}
}
if (size != -1 && t[i].size != size) {
printf("token %lu size is %d, not %d\n", i, t[i].size, size);
return 0;
}
if (s != NULL && value != NULL) {
const char *p = s + t[i].start;
if (strlen(value) != (unsigned long)(t[i].end - t[i].start) ||
strncmp(p, value, t[i].end - t[i].start) != 0) {
printf("token %lu value is %.*s, not %s\n", i, t[i].end - t[i].start,
s + t[i].start, value);
return 0;
}
}
}
}
return 1;
}
static int tokeq(const char *s, jsmntok_t *tokens, unsigned long numtok, ...) {
int ok;
va_list args;
va_start(args, numtok);
ok = vtokeq(s, tokens, numtok, args);
va_end(args);
return ok;
}
static int parse(const char *s, int status, unsigned long numtok, ...) {
int r;
int ok = 1;
va_list args;
jsmn_parser p;
jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t));
jsmn_init(&p);
r = jsmn_parse(&p, s, strlen(s), t, numtok);
if (r != status) {
printf("status is %d, not %d\n", r, status);
return 0;
}
if (status >= 0) {
va_start(args, numtok);
ok = vtokeq(s, t, numtok, args);
va_end(args);
}
free(t);
return ok;
}
#endif /* __TEST_UTIL_H__ */

1701
dependencies/nDPIsrvd.h vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,632 +0,0 @@
#!/usr/bin/env python3
import argparse
import array
import json
import re
import os
import stat
import socket
import sys
try:
from colorama import Back, Fore, Style
USE_COLORAMA=True
except ImportError:
sys.stderr.write('Python module colorama not found, using fallback.\n')
USE_COLORAMA=False
DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 7000
DEFAULT_UNIX = '/tmp/ndpid-distributor.sock'
NETWORK_BUFFER_MIN_SIZE = 6 # NETWORK_BUFFER_LENGTH_DIGITS + 1
NETWORK_BUFFER_MAX_SIZE = 33792 # Please keep this value in sync with the one in config.h
nDPId_PACKETS_PLEN_MAX = 8192 # Please keep this value in sync with the one in config.h
PKT_TYPE_ETH_IP4 = 0x0800
PKT_TYPE_ETH_IP6 = 0x86DD
class TermColor:
HINT = '\033[33m'
WARNING = '\033[93m'
FAIL = '\033[91m'
BOLD = '\033[1m'
END = '\033[0m'
BLINK = '\x1b[5m'
if USE_COLORAMA is True:
COLOR_TUPLES = [ (Fore.BLUE, [Back.RED, Back.MAGENTA, Back.WHITE]),
(Fore.CYAN, [Back.MAGENTA, Back.RED, Back.WHITE]),
(Fore.GREEN, [Back.YELLOW, Back.RED, Back.MAGENTA, Back.WHITE]),
(Fore.MAGENTA, [Back.CYAN, Back.BLUE, Back.WHITE]),
(Fore.RED, [Back.GREEN, Back.BLUE, Back.WHITE]),
(Fore.WHITE, [Back.BLACK, Back.MAGENTA, Back.RED, Back.BLUE]),
(Fore.YELLOW, [Back.RED, Back.CYAN, Back.BLUE, Back.WHITE]),
(Fore.LIGHTBLUE_EX, [Back.LIGHTRED_EX, Back.RED]),
(Fore.LIGHTCYAN_EX, [Back.LIGHTMAGENTA_EX, Back.MAGENTA]),
(Fore.LIGHTGREEN_EX, [Back.LIGHTYELLOW_EX, Back.YELLOW]),
(Fore.LIGHTMAGENTA_EX, [Back.LIGHTCYAN_EX, Back.CYAN]),
(Fore.LIGHTRED_EX, [Back.LIGHTGREEN_EX, Back.GREEN]),
(Fore.LIGHTWHITE_EX, [Back.LIGHTBLACK_EX, Back.BLACK]),
(Fore.LIGHTYELLOW_EX, [Back.LIGHTRED_EX, Back.RED]) ]
@staticmethod
def disableColor():
TermColor.HINT = ''
TermColor.WARNING = ''
TermColor.FAIL = ''
TermColor.BOLD = ''
TermColor.END = ''
TermColor.BLINK = ''
global USE_COLORAMA
USE_COLORAMA = False
@staticmethod
def calcColorHash(string):
h = 0
for char in string:
h += ord(char)
return h
@staticmethod
def getColorsByHash(string):
h = TermColor.calcColorHash(string)
tuple_index = h % len(TermColor.COLOR_TUPLES)
bg_tuple_index = h % len(TermColor.COLOR_TUPLES[tuple_index][1])
return (TermColor.COLOR_TUPLES[tuple_index][0],
TermColor.COLOR_TUPLES[tuple_index][1][bg_tuple_index])
@staticmethod
def setColorByString(string):
global USE_COLORAMA
if USE_COLORAMA is True:
fg_color, bg_color = TermColor.getColorsByHash(string)
color_hash = TermColor.calcColorHash(string)
return '{}{}{}{}{}'.format(Style.BRIGHT, fg_color, bg_color, string, Style.RESET_ALL)
else:
return '{}{}{}'.format(TermColor.BOLD, string, TermColor.END)
class ThreadData:
pass
class Instance:
def __init__(self, alias, source):
self.alias = str(alias)
self.source = str(source)
self.flows = dict()
self.thread_data = dict()
def __str__(self):
return '<%s.%s object at %s with alias %s, source %s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self)),
self.alias,
self.source
)
def getThreadData(self, thread_id):
if thread_id not in self.thread_data:
return None
return self.thread_data[thread_id]
def getThreadDataFromJSON(self, json_dict):
if 'thread_id' not in json_dict:
return None
return self.getThreadData(json_dict['thread_id'])
def getMostRecentFlowTime(self, thread_id):
return self.thread_data[thread_id].most_recent_flow_time
def setMostRecentFlowTime(self, thread_id, most_recent_flow_time):
if thread_id in self.thread_data:
return self.thread_data[thread_id]
self.thread_data[thread_id] = ThreadData()
self.thread_data[thread_id].most_recent_flow_time = most_recent_flow_time
return self.thread_data[thread_id]
def getMostRecentFlowTimeFromJSON(self, json_dict):
if 'thread_id' not in json_dict:
return 0
return self.getThreadData(json_dict['thread_id']).most_recent_flow_time
def setMostRecentFlowTimeFromJSON(self, json_dict):
if 'thread_id' not in json_dict:
return
thread_id = json_dict['thread_id']
if 'thread_ts_usec' in json_dict:
mrtf = self.getMostRecentFlowTime(thread_id) if thread_id in self.thread_data else 0
self.setMostRecentFlowTime(thread_id, max(json_dict['thread_ts_usec'], mrtf))
class Flow:
def __init__(self, flow_id, thread_id):
self.flow_id = flow_id
self.thread_id = thread_id
self.flow_last_seen = -1
self.flow_idle_time = -1
self.cleanup_reason = -1
def __str__(self):
return '<%s.%s object at %s with flow id %d>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self)),
self.flow_id
)
class FlowManager:
CLEANUP_REASON_INVALID = 0
CLEANUP_REASON_DAEMON_INIT = 1 # can happen if kill -SIGKILL $(pidof nDPId) or restart after SIGSEGV
CLEANUP_REASON_DAEMON_SHUTDOWN = 2 # graceful shutdown e.g. kill -SIGTERM $(pidof nDPId)
CLEANUP_REASON_FLOW_END = 3
CLEANUP_REASON_FLOW_IDLE = 4
CLEANUP_REASON_FLOW_TIMEOUT = 5 # nDPId died a long time ago w/o restart?
CLEANUP_REASON_APP_SHUTDOWN = 6 # your python app called FlowManager.doShutdown()
def __init__(self):
self.instances = dict()
def getInstance(self, json_dict):
if 'alias' not in json_dict or \
'source' not in json_dict:
return None
alias = json_dict['alias']
source = json_dict['source']
if alias not in self.instances:
self.instances[alias] = dict()
if source not in self.instances[alias]:
self.instances[alias][source] = Instance(alias, source)
self.instances[alias][source].setMostRecentFlowTimeFromJSON(json_dict)
return self.instances[alias][source]
@staticmethod
def getLastPacketTime(instance, flow_id, json_dict):
return max(int(json_dict['flow_src_last_pkt_time']), int(json_dict['flow_dst_last_pkt_time']), instance.flows[flow_id].flow_last_seen)
def getFlow(self, instance, json_dict):
if 'flow_id' not in json_dict:
return None
flow_id = int(json_dict['flow_id'])
if flow_id in instance.flows:
instance.flows[flow_id].flow_last_seen = FlowManager.getLastPacketTime(instance, flow_id, json_dict)
instance.flows[flow_id].flow_idle_time = int(json_dict['flow_idle_time'])
return instance.flows[flow_id]
thread_id = int(json_dict['thread_id'])
instance.flows[flow_id] = Flow(flow_id, thread_id)
instance.flows[flow_id].flow_last_seen = FlowManager.getLastPacketTime(instance, flow_id, json_dict)
instance.flows[flow_id].flow_idle_time = int(json_dict['flow_idle_time'])
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_INVALID
return instance.flows[flow_id]
def getFlowsToCleanup(self, instance, json_dict):
flows = dict()
if 'daemon_event_name' in json_dict:
if json_dict['daemon_event_name'].lower() == 'init' or \
json_dict['daemon_event_name'].lower() == 'shutdown':
# invalidate all existing flows with that alias/source/thread_id
for flow_id in instance.flows:
flow = instance.flows[flow_id]
if flow.thread_id != int(json_dict['thread_id']):
continue
if json_dict['daemon_event_name'].lower() == 'init':
flow.cleanup_reason = FlowManager.CLEANUP_REASON_DAEMON_INIT
else:
flow.cleanup_reason = FlowManager.CLEANUP_REASON_DAEMON_SHUTDOWN
flows[flow_id] = flow
for flow_id in flows:
del instance.flows[flow_id]
if len(instance.flows) == 0:
del self.instances[instance.alias][instance.source]
elif 'flow_event_name' in json_dict and \
(json_dict['flow_event_name'].lower() == 'end' or \
json_dict['flow_event_name'].lower() == 'idle' or \
json_dict['flow_event_name'].lower() == 'guessed' or \
json_dict['flow_event_name'].lower() == 'not-detected' or \
json_dict['flow_event_name'].lower() == 'detected'):
flow_id = json_dict['flow_id']
if json_dict['flow_event_name'].lower() == 'end':
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_FLOW_END
elif json_dict['flow_event_name'].lower() == 'idle':
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_FLOW_IDLE
# TODO: Flow Guessing/Detection can happen right before an idle event.
# We need to prevent that it results in a CLEANUP_REASON_FLOW_TIMEOUT.
# This may cause inconsistency and needs to be handled in another way.
if json_dict['flow_event_name'].lower() != 'guessed' and \
json_dict['flow_event_name'].lower() != 'not-detected' and \
json_dict['flow_event_name'].lower() != 'detected':
flows[flow_id] = instance.flows.pop(flow_id)
elif 'flow_last_seen' in json_dict:
if int(json_dict['flow_last_seen']) + int(json_dict['flow_idle_time']) < \
instance.getMostRecentFlowTimeFromJSON(json_dict):
flow_id = json_dict['flow_id']
instance.flows[flow_id].cleanup_reason = FlowManager.CLEANUP_REASON_FLOW_TIMEOUT
flows[flow_id] = instance.flows.pop(flow_id)
return flows
def doShutdown(self):
flows = dict()
for alias in self.instances:
for source in self.instances[alias]:
for flow_id in self.instances[alias][source].flows:
flow = self.instances[alias][source].flows[flow_id]
flow.cleanup_reason = FlowManager.CLEANUP_REASON_APP_SHUTDOWN
flows[flow_id] = flow
del self.instances
return flows
def verifyFlows(self):
invalid_flows = list()
for alias in self.instances:
for source in self.instances[alias]:
for flow_id in self.instances[alias][source].flows:
thread_id = self.instances[alias][source].flows[flow_id].thread_id
if self.instances[alias][source].flows[flow_id].flow_last_seen + \
self.instances[alias][source].flows[flow_id].flow_idle_time < \
self.instances[alias][source].getMostRecentFlowTime(thread_id):
invalid_flows += [flow_id]
return invalid_flows
class nDPIsrvdException(Exception):
UNSUPPORTED_ADDRESS_TYPE = 1
BUFFER_CAPACITY_REACHED = 2
SOCKET_CONNECTION_BROKEN = 3
INVALID_LINE_RECEIVED = 4
CALLBACK_RETURNED_FALSE = 5
SOCKET_TIMEOUT = 6
JSON_DECODE_ERROR = 7
def __init__(self, etype):
self.etype = etype
def __str__(self):
return 'nDPIsrvdException type {}'.format(self.etype)
class UnsupportedAddressType(nDPIsrvdException):
def __init__(self, addr):
super().__init__(nDPIsrvdException.UNSUPPORTED_ADDRESS_TYPE)
self.addr = addr
def __str__(self):
return '{}'.format(str(self.addr))
class BufferCapacityReached(nDPIsrvdException):
def __init__(self, current_length, max_length):
super().__init__(nDPIsrvdException.BUFFER_CAPACITY_REACHED)
self.current_length = current_length
self.max_length = max_length
def __str__(self):
return '{} of {} bytes'.format(self.current_length, self.max_length)
class SocketConnectionBroken(nDPIsrvdException):
def __init__(self):
super().__init__(nDPIsrvdException.SOCKET_CONNECTION_BROKEN)
def __str__(self):
return 'Disconnected.'
class InvalidLineReceived(nDPIsrvdException):
def __init__(self, packet_buffer):
super().__init__(nDPIsrvdException.INVALID_LINE_RECEIVED)
self.packet_buffer = packet_buffer
def __str__(self):
return 'Received JSON line is invalid.'
class CallbackReturnedFalse(nDPIsrvdException):
def __init__(self):
super().__init__(nDPIsrvdException.CALLBACK_RETURNED_FALSE)
def __str__(self):
return 'Callback returned False, abort.'
class SocketTimeout(nDPIsrvdException):
def __init__(self):
super().__init__(nDPIsrvdException.SOCKET_TIMEOUT)
def __str__(self):
return 'Socket timeout.'
class JsonDecodeError(nDPIsrvdException):
def __init__(self, json_exception, failed_line):
super().__init__(nDPIsrvdException.JSON_DECODE_ERROR)
self.json_exception = json_exception
self.failed_line = failed_line
def __str__(self):
return '{}: {}'.format(self.json_exception, self.failed_line)
class JsonFilter():
def __init__(self, filter_string):
self.filter_string = filter_string
self.filter = compile(filter_string, '<string>', 'eval')
def evaluate(self, json_dict):
if type(json_dict) is not dict:
raise nDPIsrvdException('Could not evaluate JSON Filter: expected dictionary, got {}'.format(type(json_dict)))
return eval(self.filter, {'json_dict': json_dict})
class nDPIsrvdSocket:
def __init__(self):
self.sock_family = None
self.flow_mgr = FlowManager()
self.received_bytes = 0
self.json_filter = list()
def addFilter(self, filter_str):
self.json_filter.append(JsonFilter(filter_str))
def evalFilters(self, json_dict):
for jf in self.json_filter:
try:
json_filter_retval = jf.evaluate(json_dict)
except Exception as err:
print()
sys.stderr.write('Error while evaluating expression "{}"\n'.format(jf.filter_string))
raise err
if not isinstance(json_filter_retval, bool):
print()
sys.stderr.write('Error while evaluating expression "{}"\n'.format(jf.filter_string))
raise nDPIsrvdException('JSON Filter returned an invalid type: expected bool, got {}'.format(type(json_filter_retval)))
if json_filter_retval is False:
return False
return True
def connect(self, addr):
if type(addr) is tuple:
self.sock_family = socket.AF_INET
elif type(addr) is str:
self.sock_family = socket.AF_UNIX
else:
raise UnsupportedAddressType(addr)
self.sock = socket.socket(self.sock_family, socket.SOCK_STREAM)
self.sock.connect(addr)
self.buffer = bytes()
self.msglen = 0
self.digitlen = 0
self.lines = []
self.failed_lines = []
self.filtered_lines = 0
def timeout(self, timeout):
self.sock.settimeout(timeout)
def receive(self):
if len(self.buffer) == NETWORK_BUFFER_MAX_SIZE:
raise BufferCapacityReached(len(self.buffer), NETWORK_BUFFER_MAX_SIZE)
connection_finished = False
try:
recvd = self.sock.recv(NETWORK_BUFFER_MAX_SIZE - len(self.buffer))
except ConnectionResetError:
connection_finished = True
recvd = bytes()
except TimeoutError:
raise SocketTimeout()
except socket.timeout:
raise SocketTimeout()
if len(recvd) == 0:
connection_finished = True
self.buffer += recvd
new_data_avail = False
while self.msglen + self.digitlen <= len(self.buffer):
if self.msglen == 0:
starts_with_digits = re.match(r'(^\d+){', self.buffer[:NETWORK_BUFFER_MIN_SIZE].decode(errors='strict'))
if starts_with_digits is None:
if len(self.buffer) < NETWORK_BUFFER_MIN_SIZE:
break
raise InvalidLineReceived(self.buffer)
self.msglen = int(starts_with_digits.group(1))
self.digitlen = len(starts_with_digits.group(1))
if len(self.buffer) >= self.msglen + self.digitlen:
recvd = self.buffer[self.digitlen:self.msglen + self.digitlen]
self.buffer = self.buffer[self.msglen + self.digitlen:]
self.lines += [(recvd,self.msglen,self.digitlen)]
new_data_avail = True
self.received_bytes += self.msglen + self.digitlen
self.msglen = 0
self.digitlen = 0
if connection_finished is True:
raise SocketConnectionBroken()
return new_data_avail
def parse(self, callback_json, callback_flow_cleanup, global_user_data):
retval = True
for received_line in self.lines:
try:
json_dict = json.loads(received_line[0].decode('ascii', errors='replace'), strict=True)
except json.decoder.JSONDecodeError as e:
json_dict = dict()
self.failed_lines += [received_line]
self.lines = self.lines[1:]
raise JsonDecodeError(e, received_line)
instance = self.flow_mgr.getInstance(json_dict)
if instance is None:
self.failed_lines += [received_line]
retval = False
continue
current_flow = self.flow_mgr.getFlow(instance, json_dict)
filter_eval = self.evalFilters(json_dict)
if filter_eval is True:
try:
if callback_json(json_dict, instance, current_flow, global_user_data) is not True:
self.failed_lines += [received_line]
retval = False
except Exception as e:
self.failed_lines += [received_line]
self.lines = self.lines[1:]
raise(e)
else:
self.filtered_lines += 1
for _, flow in self.flow_mgr.getFlowsToCleanup(instance, json_dict).items():
if callback_flow_cleanup is None:
pass
elif filter_eval is True and callback_flow_cleanup(instance, flow, global_user_data) is not True:
self.failed_lines += [received_line]
self.lines = self.lines[1:]
retval = False
self.lines = self.lines[1:]
return retval
def loop(self, callback_json, callback_flow_cleanup, global_user_data):
throw_ex = None
while True:
bytes_recv = 0
try:
bytes_recv = self.receive()
except Exception as err:
throw_ex = err
if self.parse(callback_json, callback_flow_cleanup, global_user_data) is False:
raise CallbackReturnedFalse()
if throw_ex is not None:
raise throw_ex
def shutdown(self):
return self.flow_mgr.doShutdown().items()
def verify(self):
if len(self.failed_lines) > 0:
raise nDPIsrvdException('Failed lines > 0: {}'.format(len(self.failed_lines)))
return self.flow_mgr.verifyFlows()
def defaultArgumentParser(desc='nDPIsrvd Python Interface', enable_json_filter=False,
help_formatter=argparse.ArgumentDefaultsHelpFormatter):
parser = argparse.ArgumentParser(description=desc, formatter_class=help_formatter)
parser.add_argument('--host', type=str, help='nDPIsrvd host IP')
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help='nDPIsrvd TCP port')
parser.add_argument('--unix', type=str, help='nDPIsrvd unix socket path')
if enable_json_filter is True:
parser.add_argument('--filter', type=str, action='append',
help='Set a filter string which if evaluates to True will invoke the JSON callback.\n'
'Example: json_dict[\'flow_event_name\'] == \'detected\' will only process \'detected\' events.')
return parser
def toSeconds(usec):
return usec / (1000 * 1000)
def validateAddress(args):
tcp_addr_set = False
address = None
if args.host is None:
address_tcpip = (DEFAULT_HOST, args.port)
else:
address_tcpip = (args.host, args.port)
tcp_addr_set = True
if args.unix is None:
address_unix = DEFAULT_UNIX
else:
address_unix = args.unix
possible_sock_mode = 0
try:
possible_sock_mode = os.stat(address_unix).st_mode
except:
pass
if tcp_addr_set == False and stat.S_ISSOCK(possible_sock_mode):
address = address_unix
else:
address = address_tcpip
return address
def prepareJsonFilter(args, nsock):
# HowTo use JSON Filters:
# Add `--filter [FILTER_STRING]` to the Python scripts that support JSON filtering.
# Examples:
# ./examples/py-json-stdout/json-stdout.py --filter '"ndpi" in json_dict and "proto" in json_dict["ndpi"]'
# The command above will print only JSONs that have the subobjects json_dict["ndpi"] and json_dict["ndpi"]["proto"] available.
# ./examples/py-flow-info/flow-info.py --filter 'json_dict["source"] == "eth0"' --filter '"flow_event_name" in json_dict and json_dict["flow_event_name"] == "analyse"'
# Multiple JSON filter will be ANDed together.
# Note: You may *only* use the global "json_dict" in your expressions.
try:
json_filter = args.filter
if json_filter is not None:
for jf in json_filter:
nsock.addFilter(jf)
except AttributeError:
pass
global schema
schema = {'packet_event_schema' : None, 'error_event_schema' : None, 'daemon_event_schema' : None, 'flow_event_schema' : None}
def initSchemaValidator(schema_dirs=[]):
if len(schema_dirs) == 0:
schema_dirs += [os.path.dirname(sys.argv[0]) + '/../../schema']
schema_dirs += [os.path.dirname(sys.argv[0]) + '/../share/nDPId']
schema_dirs += [sys.base_prefix + '/share/nDPId']
for key in schema:
for schema_dir in schema_dirs:
try:
with open(schema_dir + '/' + str(key) + '.json', 'r') as schema_file:
schema[key] = json.load(schema_file)
except FileNotFoundError:
continue
else:
break
def validateAgainstSchema(json_dict):
import jsonschema
if 'packet_event_id' in json_dict:
try:
jsonschema.Draft7Validator(schema=schema['packet_event_schema']).validate(instance=json_dict)
except AttributeError:
jsonschema.validate(instance=json_dict, schema=schema['packet_event_schema'])
return True
if 'error_event_id' in json_dict:
try:
jsonschema.Draft7Validator(schema=schema['error_event_schema']).validate(instance=json_dict)
except AttributeError:
jsonschema.validate(instance=json_dict, schema=schema['error_event_schema'])
return True
if 'daemon_event_id' in json_dict:
try:
jsonschema.Draft7Validator(schema=schema['daemon_event_schema']).validate(instance=json_dict)
except AttributeError:
jsonschema.validate(instance=json_dict, schema=schema['daemon_event_schema'])
return True
if 'flow_event_id' in json_dict:
try:
jsonschema.Draft7Validator(schema=schema['flow_event_schema']).validate(instance=json_dict)
except AttributeError:
jsonschema.validate(instance=json_dict, schema=schema['flow_event_schema'])
return True
return False

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env sh
MYDIR="$(dirname ${0})"
cd "${MYDIR}/.."
git subtree pull --squash --prefix=dependencies/jsmn https://github.com/zserge/jsmn.git master

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env sh
MYDIR="$(dirname ${0})"
cd "${MYDIR}/.."
git subtree pull --squash --prefix=dependencies/uthash https://github.com/troydhanson/uthash.git master

View File

@@ -1,3 +0,0 @@
*~
*.out
keystat.*

View File

@@ -1,14 +0,0 @@
language: cpp
matrix:
include:
- os: linux
compiler: gcc
- os: linux
compiler: clang
- os: osx
script:
- make -C tests EXTRA_CFLAGS="-W -Wall -Wextra -Wswitch-default"
- make -C tests clean ; make -C tests pedantic
- make -C tests clean ; make -C tests pedantic EXTRA_CFLAGS=-DNO_DECLTYPE
- make -C tests clean ; make -C tests cplusplus
- make -C tests clean ; make -C tests cplusplus EXTRA_CFLAGS=-DNO_DECLTYPE

View File

@@ -1,21 +0,0 @@
Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,8 +0,0 @@
[![Build status](https://api.travis-ci.org/troydhanson/uthash.svg?branch=master)](https://travis-ci.org/troydhanson/uthash)
Documentation for uthash is available at:
https://troydhanson.github.io/uthash/

View File

@@ -1,7 +0,0 @@
ChangeLog.html
userguide.html
utarray.html
utlist.html
utringbuffer.html
utstack.html
utstring.html

View File

@@ -1,282 +0,0 @@
uthash ChangeLog
================
Click to return to the link:index.html[uthash home page].
NOTE: This ChangeLog may be incomplete and/or incorrect. See the git commit log.
Version 2.3.0 (2021-02-25)
--------------------------
* remove HASH_FCN; the HASH_FUNCTION and HASH_KEYCMP macros now behave similarly
* remove uthash_memcmp (deprecated in v2.1.0) in favor of HASH_KEYCMP
* silence -Wswitch-default warnings (thanks, Olaf Bergmann!)
Version 2.2.0 (2020-12-17)
--------------------------
* add HASH_NO_STDINT for platforms without C99 <stdint.h>
* silence many -Wcast-qual warnings (thanks, Olaf Bergmann!)
* skip hash computation when finding in an empty hash (thanks, Huansong Fu!)
* rename oom to utarray_oom, in utarray.h (thanks, Hong Xu!)
* rename oom to utstring_oom, in utstring.h (thanks, Hong Xu!)
* remove MurmurHash/HASH_MUR
Version 2.1.0 (2018-12-20)
--------------------------
* silence some Clang static analysis warnings
* add LL_INSERT_INORDER and LL_LOWER_BOUND etc (thanks, Jeffrey Lovitz and Mattias Eriksson!)
* add uthash_bzero for platforms without <string.h>
* fix a missing HASH_BLOOM_ADD in HASH_SELECT (thanks, Pawel Veselov!)
* permit malloc failure to be recoverable via HASH_NONFATAL_OOM (thanks, Pawel Veselov!)
* avoid repeated calls to uthash_strlen in HASH_FIND_STR
* rename uthash_memcmp to HASH_KEYCMP, leaving the old name for compatibility
* add utstack.h
* remove libut
Version 2.0.2 (2017-03-02)
--------------------------
* fix segfault in HASH_ADD_INORDER etc (thanks, Yana Kireyonok!)
* remove spurious cast to unsigned in utstring_len (thanks, Michal Sestrienka!)
* add uthash_memcmp and uthash_strlen for platforms without <stdlib.h> (thanks, Pawel Veselov!)
* fix a C++ incompatibility in utringbuffer
Version 2.0.1 (2016-07-05)
--------------------------
* in-order insertion macros HASH_ADD_INORDER etc (thanks, Thilo Schulz!)
* by-hashvalue insertion macros HASH_ADD_BYHASHVALUE etc
* during key comparison, check hashvalue before doing a full memcmp
* add utringbuffer.h
Version 1.9.9.1 (2014-11-18)
----------------------------
* inclusion of experimental libut bundle with utvector in opt/
* use shift in Bernstein hash instead of multiply (thanks, Jimmy Zhuo!)
* switch ssize_t types in utarray/utstring to size_t (thanks, Hong Xu!)
* fix utstring pointer math on >4GB strings (thanks, Thomas Bottesch!)
* change FNV hash to FNV-1a varation (thanks, dwest1975!)
* fix bug in HASH_REPLACE_STR (thanks, Ilya Kaliman!)
Version 1.9.9 (2014-02-25)
--------------------------
* made HASH_ADD_STR compatible with char* or char[] (thanks, Samuel Thibault!)
* fixed header inclusion of sys/types.h for ssize_t (thanks, Fernando Campos!)
* added LL_COUNT/DL_COUNT/CDL_COUNT (thansk, Paul Praet!)
* added LRU cache example in `tests/lru_cache` (thanks, Oliver Lorenz!)
* fix LL_DELETE2 for VS2008 (thanks, Greg Davydouski!)
* fix missing argument in `HASH_REPLACE_STR` (thanks, Alex!)
* bump version number in source files to match docs (thanks, John Crow!)
* add `HASH_OVERHEAD` macro to get overhead size for hash table
Version 1.9.8 (2013-03-10)
--------------------------
* `HASH_REPLACE` now in uthash (thanks, Nick Vatamaniuc!)
* fixed clang warnings (thanks wynnw!)
* fixed `utarray_insert` when inserting past array end (thanks Rob Willett!)
* you can now find http://troydhanson.github.io/uthash/[uthash on GitHub]
* there's a https://groups.google.com/d/forum/uthash[uthash Google Group]
* uthash has been downloaded 29,000+ times since 2006 on SourceForge
Version 1.9.7 (2012-10-09)
--------------------------
* utstring now supports substring search using `utstring_find` (thanks, Joe Wei!)
* utlist now supports element 'prepend' and 'replace' (thanks, Zoltán Lajos Kis!)
* utlist element prev/next fields can now have any names (thanks, Pawel S. Veselov!)
* uthash cast quiets a clang warning (thanks, Roman Divacky and Baptiste Daroussin!)
* uthash userguide example shows how to check key uniqueness (thanks, Richard Cook!)
* uthash HASH_MUR compiles under MSVC++ 10 in C mode (thanks, Arun Kirthi Cherian!)
* `utstring_printf` now supports format checking (thanks, Donald Carr!)
Version 1.9.6 (2012-04-28)
--------------------------
* add utarray_prev (thanks, Ben Hiett!)
* add parens/casts for greater compatibility (thanks, Atis, Debasis Ganguly, and Steve McClellan!)
* added ifndef to uthash_malloc and related hooks (thanks, Holger Machens!)
* edit examples so they do not leak memory (thanks, 任晶磊!)
Version 1.9.5 (2011-11-16)
--------------------------
* added `utarray_renew`
* fixed memory leak in `uthash_clear` when using Bloom filter (thanks, Jan Hättig!)
* utarray now copies the UT_icd on array creation rather than storing a pointer
* add parentheses to `HASH_ADD` to fix preprocessing of certain arguments (thanks, Aaron Rosen!)
* more parenthesizations for greater macro argument flexibility
Version 1.9.4 (2011-06-05)
--------------------------
* uthash now supports MurmurHash v3
* utlist now includes concatenation macros (`LL_CONCAT` and `DL_CONCAT`)
* utarray now supports binary search (`utarray_find`)
* utstring now supports a new-or-clear-existing macro (`utstring_renew`)
* documented technique for a multi-level hash table
* clarified scope requirements for `UT_icd` in the utarray documentation
* fixed termination when `utstring_clear` is followed by `utstring_body`
* fixed utarray_inserta macro when used with complex arguments
* on Visual Studio define missing type `uint8_t`
* Debian/Ubuntu include uthash in the package `uthash-dev`.
* uthash has been downloaded 16,211 times.
Thanks to Yu Feng, Richard Cook, Dino Ciuffetti, Chris Groer, and Arun Cherian
for feedback and fixes in this release!
Version 1.9.3 (2010-10-31)
--------------------------
* fix an `ifdef` for compatibility with Intel compiler (thanks, degski!)
* fix `HASH_ITER` macro to satisfy C++ casting rules (thanks, Erik Bai!)
Version 1.9.2 (2010-10-04)
--------------------------
* new `HASH_ITER` macro for more convenient deletion-safe iteration
* `hashscan` can now run on FreeBSD 8.1 and later (thanks, Markus Gebert!)
* More parens to evaluate complex macro arguments properly (thanks, ngg!)
* Add sz parameter to the `uthash_free` hook for platforms that do their own memory management. Hopefully this minor API change doesn't cause too much breakage for people. (thanks, Niall Douglas!)
* uthash has been downloaded 12,294 times
Version 1.9.1 (2010-05-15)
--------------------------
* Fix a redefinition warning when using `uthash.h` and `utstring.h` together
* Fix a bug in `utstring_init`
* Added `HASH_FIND_PTR` and `HASH_ADD_PTR` (thanks, Niall Douglas!)
Version 1.9 (2010-03-31)
--------------------------
* uthash now supports Visual Studio 2008 and 2010 in C or C++ code!
* new headers link:utarray.html[utarray.h] and link:utstring.html[utstring.h]
are now included. These implement dynamic arrays and strings using macros
* link:utlist.html[utlist.h] now has deletion-safe iterators and search macros
* the test suite now runs under Visual Studio (thanks again degski!)
* special thanks for suggesting utarray and utlist features to Charalampos P.!
* uthash has been downloaded 9,616 times
Version 1.8 (2009-09-08)
--------------------------
* Added the `hashscan` utility that can report on the size and quality of
hash tables in a running process (Linux-only)
* Added Bloom filter support. This has the potential to speed up certain
types of programs that look up non-existant keys in sufficient numbers.
* Restored the MurmurHash, which can once again be used, if an additional
symbol is defined. This is a "safety" by which the user declares they
understand that `-fno-strict-aliasing` flag must be used if they are
using MurmurHash under gcc with optimization.
* Unified the bucket/table malloc hooks; now there is only one malloc hook
* Re-organized the manual into a main section and advanced topics section
* Fixed a bug in `utlist.h` where sorting a singly-linked list threw a
compile-time error.
* Fixed a bug in `utlist.h` where a doubly-linked list that is sorted
did not maintain the special `head->prev` pointer back to the list tail.
Version 1.7 (2009-06-11)
--------------------------
* The MurmurHash has been removed, and Jenkin's hash is once again the default.
While MurmurHash performed well, it's unsafe with regard to the strict
aliasing rule. This results in incorrect code when compiled with optimization.
It's not possible to enable `-fno-strict-aliasing` from within a header file.
* The linked list macros in `utlist.h` now comply with the strict-aliasing
rule so they generate correct code under high optimization levels (O2 or O3).
The use of the `__typeof__` extension, which was originally a GNU extension,
may reduce portability to other compilers that do not support this extension.
This extension is used in the singly-linked list macros and the sort macros.
Version 1.6 (2009-05-08)
--------------------------
Special thanks to Alfred Heisner for contributing several enhancements:
* Support for two new hash functions:
- the Paul Hsieh hash function (`HASH_SFH`)
- Austin Appleby's MurmurHash function (`HASH_MUR`)
* Because of its excellent performance, MurmurHash is now the default hash function.
* `keystats` now has much better elapsed time accuracy under Cygwin and MinGW
* fixed casting in `HASH_FNV`, `HASH_SAX` and `HASH_OAT` for non-char keys
This release also includes:
* a new `HASH_CLEAR` operation clears a hash table in one step.
* a new `HASH_SELECT` operation inserts those elements from one hash that
satisfy a given condition into another hash. The selected items have
dual presence in both hash tables. For example a game could select the
visible polygons from a hash of all polygons.
* fixed a compile-time error which occurred if the final argument to
`HASH_ADD_KEYPTR` was a pointer to an array member like `&a[i]`
* added another test script `tests/all_funcs` which executes the test suite
using every supported hash function
And lastly,
* a new, separate header called link:utlist.html[utlist.h] is included which
provides 'linked list macros' for C structures, similar in style to the
uthash macros
Version 1.5 (2009-02-19)
--------------------------
* now thread-safe for concurrent readers
* use scratch variables on stack rather than in table (thanks, Petter Arvidsson!).
This change made HASH_FIND about 13% faster and enabled reader concurrency.
* made link:license.html[BSD license] terms even more permissive
* added link:userguide.pdf[PDF version] of User Guide
* added http://troydhanson.wordpress.com/feed/[update news] image:rss.png[(RSS)]
Version 1.4 (2008-09-23)
--------------------------
* Add `HASH_COUNT` for counting items in the hash
* Compatibility with C\+\+. Satisfy additional casting requirements.
Also in the `tests/` directory, running `make cplusplus` now compiles
all the test programs with the C++ compiler.
* Eliminate `elmt` pointer from the UT_hash_handle. Calculate elmt
from hash handle address by subtracting `hho` (hash handle offset).
* Contributed by L.S.Chin:
Cast `void*` to char* before pointer arithmetic to suppress compiler
warnings. We assume compilers abide to C standards which impose
requirement that `sizeof(void*) == sizeof(char*)`.
* Return meaningful exit status from do_tests per Tiago Cunha,
so that package manager-based install can verify tests are successful
Version 1.3 (2008-07-27)
------------------------
* use integer-only math-- no floating point! Support FPU-less CPU's.
* eliminate `hash_q` metric, which measured the fraction of items with
non-ideal chain positions. We only need to know if this fraction
is below 0.5. This is now determined using fast bitwise tests.
* when an item is added to the hash, calculate the key's hash value
upfront and store it, instead of recomputing it as needed. This hashv
is stored in the hash handle. Potentially major speed benefit for
bucket expansion algorithm. Deleting is marginally improved too.
* fixed a minor bug in the calculation of the max ideal chain length;
line 446 in v1.2 erroneously calculated a/b*2 instead of a/(b*2).
The effect of this bug was that bucket expansion could occur more
readily because the per-bucket 'max chain length multiplier factor'
(which delays bucket expansion when certain buckets are overused)
was set to a lower, expansion-favoring value than intended.
* improved source commenting and improved variable names in structures
* remove `HASH_JSW`. Lengthy random number array made code less readable
* add `HASH_SRT(hh,hash,cmp)` as a generalized `HASH_SORT(hash,cmp)`.
It was an omission in uthash 1.2 that there was no sort macro for
hash handles with names other than hh.
* Corrected `HASH_FSCK` so it works with any name for the hash handle.
* behave properly in pathological `HASH_DEL(a,a)` case where the same
variable references the head and the deletee (advancing the head
then loses the correct reference to the deletee); fix by using
scratch area in the hash table to store deletee hash handle.
* made tests runnable on MinGW
* 3000+ downloads since uthash-1.0
Version 1.2 (2006-11-22)
------------------------
* new `HASH_SORT` macro
* Cygwin support
* User Guide now features a clickable Table of Contents.
(The technique for generating the TOC on the browser was contributed
back to the AsciiDoc project and incorporated into AsciiDoc v8.1.0).
Version 1.1 (2006-06-28)
------------------------
* uthash-1.1 released
* supports several built-in user-selectable hash functions
* new keystats utility quantifies performance of hash functions
Version 1.0 (2006-06-02)
------------------------
* Initial release
// vim: set syntax=asciidoc:

View File

@@ -1,18 +0,0 @@
HTML=$(patsubst %.txt,%.html,$(wildcard *.txt))
all: $(HTML)
# when each target of a multi-target rule has its own prereq
# we use a static pattern rule.
$(HTML): %.html: %.txt
asciidoc -a toc2 $<
TMP=/tmp/uthash-gh-pages
stage:
mkdir -p ${TMP}
rm -if ${TMP}/*
cp *.html *.css *.png ${TMP}
.PHONY: clean
clean:
$(RM) $(HTML)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,451 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="728px"
height="90px"
id="svg1307"
sodipodi:version="0.32"
inkscape:version="0.45.1"
sodipodi:docbase="/Users/thanson/code/uthash/trunk/doc/html/img"
sodipodi:docname="banner.svg"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/banner.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs1309">
<linearGradient
id="linearGradient12743">
<stop
style="stop-color:#99e1fa;stop-opacity:1;"
offset="0"
id="stop12745" />
<stop
id="stop12753"
offset="0"
style="stop-color:#99e1fa;stop-opacity:0.49803922;" />
<stop
style="stop-color:#99e1fa;stop-opacity:0;"
offset="1"
id="stop12747" />
</linearGradient>
<marker
inkscape:stockid="Arrow1Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Mend"
style="overflow:visible;">
<path
id="path7755"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
transform="scale(0.4) rotate(180)" />
</marker>
<marker
inkscape:stockid="Arrow1Sstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Sstart"
style="overflow:visible">
<path
id="path7752"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Send"
style="overflow:visible;">
<path
id="path7749"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
transform="scale(0.2) rotate(180)" />
</marker>
<marker
inkscape:stockid="StopM"
orient="auto"
refY="0.0"
refX="0.0"
id="StopM"
style="overflow:visible">
<path
id="path7651"
d="M 0.0,5.65 L 0.0,-5.65"
style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
transform="scale(0.4)" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow2Mend"
style="overflow:visible;">
<path
id="path7737"
style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) rotate(180) translate(-5,0)" />
</marker>
<marker
inkscape:stockid="TriangleInM"
orient="auto"
refY="0.0"
refX="0.0"
id="TriangleInM"
style="overflow:visible">
<path
id="path7669"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
transform="scale(-0.4)" />
</marker>
<marker
inkscape:stockid="StopL"
orient="auto"
refY="0.0"
refX="0.0"
id="StopL"
style="overflow:visible">
<path
id="path7654"
d="M 0.0,5.65 L 0.0,-5.65"
style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
transform="scale(0.8)" />
</marker>
<marker
inkscape:stockid="TriangleOutM"
orient="auto"
refY="0.0"
refX="0.0"
id="TriangleOutM"
style="overflow:visible">
<path
id="path7660"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
transform="scale(0.4)" />
</marker>
<marker
inkscape:stockid="DiamondS"
orient="auto"
refY="0.0"
refX="0.0"
id="DiamondS"
style="overflow:visible">
<path
id="path7675"
d="M -2.1579186e-005,-7.0710768 L -7.0710894,-8.9383918e-006 L -2.1579186e-005,7.0710589 L 7.0710462,-8.9383918e-006 L -2.1579186e-005,-7.0710768 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
transform="scale(0.2)" />
</marker>
<marker
inkscape:stockid="Tail"
orient="auto"
refY="0.0"
refX="0.0"
id="Tail"
style="overflow:visible">
<g
id="g7716"
transform="scale(-1.2)">
<path
id="path7718"
d="M -3.8048674,-3.9585227 L 0.54352094,-0.00068114835"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path7720"
d="M -1.2866832,-3.9585227 L 3.0617053,-0.00068114835"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path7722"
d="M 1.3053582,-3.9585227 L 5.6537466,-0.00068114835"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path7724"
d="M -3.8048674,4.1775838 L 0.54352094,0.21974226"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path7726"
d="M -1.2866832,4.1775838 L 3.0617053,0.21974226"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
<path
id="path7728"
d="M 1.3053582,4.1775838 L 5.6537466,0.21974226"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;marker-start:none;marker-end:none;stroke-linecap:round" />
</g>
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Lstart"
style="overflow:visible">
<path
id="path7764"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
transform="scale(0.8)" />
</marker>
<linearGradient
inkscape:collect="always"
id="linearGradient3964">
<stop
style="stop-color:#00eb00;stop-opacity:1;"
offset="0"
id="stop3966" />
<stop
style="stop-color:#00eb00;stop-opacity:0;"
offset="1"
id="stop3968" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3964"
id="radialGradient3996"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.237347,4.901628e-13,36.5688)"
cx="176.99219"
cy="47.949429"
fx="176.99219"
fy="47.949429"
r="78.257812" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient12743"
id="radialGradient12751"
cx="165.91866"
cy="45.584854"
fx="165.91866"
fy="45.584854"
r="56.51194"
gradientTransform="matrix(1,0,0,0.603517,0,18.07364)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.9793956"
inkscape:cx="372.32157"
inkscape:cy="45"
inkscape:document-units="px"
inkscape:current-layer="g2335"
inkscape:window-width="791"
inkscape:window-height="581"
inkscape:window-x="4"
inkscape:window-y="48" />
<metadata
id="metadata1312">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<rect
style="opacity:1;fill:#393be9;fill-opacity:1;stroke:#f18a00;stroke-width:5.65522385;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3981"
width="435.17825"
height="78.666664"
x="5.1747785"
y="6"
rx="29.141403"
ry="20"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<flowRoot
transform="matrix(1.673678,0,0,1.673678,-141.8484,-37.12273)"
style="font-size:47.99999774;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr;text-anchor:start;fill:#faf599;fill-opacity:1;stroke:#f3bf33;stroke-opacity:1;font-family:Bitstream Vera Sans"
id="flowRoot3988"
xml:space="preserve"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><flowRegion
style="fill:url(#radialGradient3996);fill-opacity:1"
id="flowRegion3990"><rect
style="font-size:47.99999774;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr;text-anchor:start;fill:#faf599;fill-opacity:1;stroke:#f3bf33;stroke-opacity:1;font-family:Bitstream Vera Sans"
y="18"
x="94.666664"
height="61.333332"
width="321.33334"
id="rect3992" /></flowRegion><flowPara
id="flowPara7831">ut hash</flowPara></flowRoot> <rect
style="opacity:1;fill:url(#radialGradient12751);fill-opacity:1.0;stroke:none;stroke-width:2.82532263;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect10995"
width="113.02388"
height="68.211792"
x="109.40672"
y="11.478957"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<g
id="g7808"
transform="matrix(0.807859,0,0,0.807859,-140.848,9.677403)"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<rect
y="37.730064"
x="382.39673"
height="18.405188"
width="23.206543"
id="rect4882"
style="opacity:1;fill:#9be5ea;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:#d48c21;fill-opacity:0.97777776;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4886"
width="23.206543"
height="18.405188"
x="416.39673"
y="37.730064" />
<path
inkscape:connector-type="polyline"
id="path4890"
d="M 372.60327,46.932658 L 381.39673,46.932658"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path4892"
d="M 406.60327,46.932658 L 415.39673,46.932658"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="9.7300644"
x="348.39673"
height="18.405188"
width="23.206543"
id="rect4896"
style="opacity:1;fill:#79c71a;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:#f5e1a2;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4898"
width="23.206543"
height="18.405188"
x="382.39673"
y="9.7300644" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 372.60327,18.932658 L 381.39673,18.932658"
id="path4902"
inkscape:connector-type="polyline" />
<rect
y="14.207111"
x="318.45328"
height="10.1194"
width="10.1194"
id="rect4906"
style="opacity:1;fill:#1336e6;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path5789"
d="M 328.57268,19.220474 L 347.39673,19.048081"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 328.57268,19.220474 L 347.39673,19.048081"
id="path5795"
inkscape:connector-type="polyline" />
<rect
y="37.789951"
x="348.20978"
height="18.405188"
width="23.206543"
id="rect5803"
style="opacity:1;fill:#e5e5e5;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
y="42.267002"
x="318.26633"
height="10.1194"
width="10.1194"
id="rect5805"
style="opacity:1;fill:#1336e6;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 328.38572,47.280365 L 347.20977,47.107972"
id="path5807"
inkscape:connector-type="polyline" />
<rect
style="opacity:1;fill:#ddf9ed;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5809"
width="23.206543"
height="18.405188"
x="348.20978"
y="63.720913" />
<rect
style="opacity:1;fill:#1336e6;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5811"
width="10.1194"
height="10.1194"
x="318.26633"
y="68.197968" />
<path
inkscape:connector-type="polyline"
id="path5813"
d="M 328.38572,73.211328 L 347.20977,73.038935"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path5833"
d="M 323.47927,24.326511 L 323.35974,42.267002"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#2f29df;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path5835"
d="M 323.32603,52.386402 L 323.32603,68.197968"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#2f29df;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path6716"
d="M 429.08836,47.11641 L 394.37307,18.527349 L 394.37307,49.158485 L 359.65778,18.527349 L 359.65778,50.179523 L 359.65778,75.70547"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#f3bf33;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#StopM);marker-end:url(#Arrow1Send);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
<g
id="g2335"
transform="translate(0,-10)"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo_tag.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<text
xml:space="preserve"
style="font-size:18.43119621px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
x="565.8512"
y="50.633156"
id="text2331"><tspan
sodipodi:role="line"
id="tspan2333"
x="565.85119"
y="50.633156">a hash table</tspan><tspan
sodipodi:role="line"
x="565.8512"
y="73.672151"
id="tspan2361">for C structures</tspan></text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1 +0,0 @@
google-site-verification: google315d692c9c632ed0.html

View File

@@ -1,122 +0,0 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XTHML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" type="text/css" href="styles.css" />
<title>uthash: a hash table for C structures</title>
</head>
<body>
<div id="banner">
<img src="banner.png" alt="uthash: a hash table for C structures" />
</div> <!-- banner -->
<div id="topnav">
<a href="http://github.com/troydhanson/uthash">GitHub page</a> &gt;
uthash home <!-- http://troydhanson.github.io/uthash/ -->
<a href="https://twitter.com/share" class="twitter-share-button" data-via="troydhanson">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</div>
<hr />
<div id="mid">
<div id="nav">
<h2>documentation</h2>
<div><a href="userguide.html">uthash</a></div>
<div><a href="utlist.html">utlist</a></div>
<div><a href="utarray.html">utarray</a></div>
<div><a href="utringbuffer.html">utringbuffer</a></div>
<div><a href="utstack.html">utstack</a></div>
<div><a href="utstring.html">utstring</a></div>
<h2>download</h2>
<h3>GNU/Linux, Windows</h3>
<div><a href=https://github.com/troydhanson/uthash/archive/master.zip>uthash-master.zip</a></div>
<div><a href=https://github.com/troydhanson/uthash>git clone</a></div>
<h2>license</h2>
<div><a href="license.html">BSD revised</a></div>
<h2>developer</h2>
<div><a href="http://troydhanson.github.io/">Troy D. Hanson</a></div>
<h2>maintainer</h2>
<div><a href="https://github.com/Quuxplusone">Arthur O'Dwyer</a></div>
</div>
<div id="main">
Any C structure can be stored in a hash table using uthash. Just add a
<em>UT_hash_handle</em> to the structure and choose one or more fields
in your structure to act as the key. Then use these macros to store,
retrieve or delete items from the hash table.
<div class="listing">
Example 1. Adding an item to a hash.
<div class="code">
<pre>
#include "uthash.h"
struct my_struct {
int id; /* we'll use this field as the key */
char name[10];
UT_hash_handle hh; /* makes this structure hashable */
};
struct my_struct *users = NULL;
void add_user(struct my_struct *s) {
HASH_ADD_INT(users, id, s);
}
</pre>
</div> <!-- code -->
</div> <!-- listing -->
<div class="listing">
Example 2. Looking up an item in a hash.
<div class="code">
<pre>
struct my_struct *find_user(int user_id) {
struct my_struct *s;
HASH_FIND_INT(users, &amp;user_id, s);
return s;
}
</pre>
</div> <!-- code -->
</div> <!-- listing -->
<div class="listing">
Example 3. Deleting an item from a hash.
<div class="code">
<pre>
void delete_user(struct my_struct *user) {
HASH_DEL(users, user);
}
</pre>
</div> <!-- code -->
</div> <!-- listing -->
For more information and examples, please see the <a href="userguide.html">User Guide.</a>
</div> <!-- main -->
</div> <!-- mid -->
<hr />
<div id="footer">
</div> <!-- footer -->
</body>
</html>

View File

@@ -1,55 +0,0 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XTHML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" type="text/css" href="styles.css" />
<title>uthash: a hash table for C structures</title>
</head>
<body>
<div id="banner">
<img src="banner.png" alt="uthash: a hash table for C structures" />
</div> <!-- banner -->
<div id="topnav">
<a href="http://troydhanson.github.io/uthash/">uthash home</a> &gt;
BSD license
</div>
<hr />
<div id="mid">
<div id="main">
<pre>
Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>
</div> <!-- mid -->
</div> <!-- main -->
<hr />
<div id="footer">
</div> <!-- footer -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

View File

@@ -1,140 +0,0 @@
#banner {
/* font-size: x-large; */
/* background: #ff00ff; */
/* height: 100px; */
}
#topnav {
/* background-image: url(img/grad_topnav.png); */
/* background-repeat: repeat-y; */
/* background-color: #af00af; */
/* height: 25px; */
margin-top: 5px;
margin-bottom: 10px;
padding: 3px;
font-size: 9pt;
font-family: sans-serif;
/* border-style: solid; */
/* border-width: 1px; */
}
#topnav a {
padding: 8px;
}
h1,p { margin: 0; } /* non-0 margin on firefox */
#mid {
/* background-image: url(img/grad_blue.png); */
background-repeat: repeat-y;
/* background-color: #ffddaa; */
padding-top: 20px;
margin-bottom: 10px;
}
#mid img {
padding-left: 10px;
vertical-align: middle;
}
a img {
border: 0
}
.twitter-share-button {
float: right;
}
.twitter-follow-button {
padding: 5px;
}
#nav {
background-color: #fff8f1;
margin-left: 10px;
margin-top: 20px;
margin-right: 20px;
float: left;
padding: 10px;
border-style: solid;
border-width: 2px;
font-family: sans-serif;
}
#nav h2 {
font-weight: bold;
font-size: 10pt;
}
#nav h3 {
/* font-weight: bold; */
padding-left: 5px;
/* font-style: oblique; */
font-family: sans-serif;
font-size: 7pt;
}
#nav div {
font-size: 9pt;
padding-left: 15px;
}
#main {
background: #ffffff;
margin-top: 20px;
margin-left: 170px;
padding-left: 20px;
height: 100%;
}
#main h1 {
font-family: sans-serif;
}
.listing {
margin: 20px;
font-family: sans-serif;
font-weight: bold;
}
.code {
padding: 10px;
margin: 10px;
font-size: 8pt;
font-weight: normal;
background: #f3f3f3;
width: 100%;
border-style: solid;
border-width: 1px;
}
#footer {
/* background: #00ffff; */
margin-top: 5px;
font-size: small;
font-family: sans-serif;
}
hr {
height: 0.04em;
background: black;
margin: 0 10% 0 0;
}
#footer {
width: 90%;
}
#footer img {
margin-right: 5px;
float: right;
}
#footer #support {
float: right;
}
body {
width: 80%;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,383 +0,0 @@
utarray: dynamic array macros for C
===================================
Troy D. Hanson <tdh@tkhanson.net>
v2.3.0, February 2021
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
Introduction
------------
A set of general-purpose dynamic array macros for C structures are included with
uthash in `utarray.h`. To use these macros in your own C program, just
copy `utarray.h` into your source directory and use it in your programs.
#include "utarray.h"
The dynamic array supports basic operations such as push, pop, and erase on the
array elements. These array elements can be any simple datatype or structure.
The array <<operations,operations>> are based loosely on the C++ STL vector methods.
Internally the dynamic array contains a contiguous memory region into which
the elements are copied. This buffer is grown as needed using `realloc` to
accommodate all the data that is pushed into it.
Download
~~~~~~~~
To download the `utarray.h` header file,
follow the links on https://github.com/troydhanson/uthash to clone uthash or get a zip file,
then look in the src/ sub-directory.
BSD licensed
~~~~~~~~~~~~
This software is made available under the
link:license.html[revised BSD license].
It is free and open source.
Platforms
~~~~~~~~~
The 'utarray' macros have been tested on:
* Linux,
* Mac OS X,
* Windows, using Visual Studio 2008 and Visual Studio 2010
Usage
-----
Declaration
~~~~~~~~~~~
The array itself has the data type `UT_array`, regardless of the type of
elements to be stored in it. It is declared like,
UT_array *nums;
New and free
~~~~~~~~~~~~
The next step is to create the array using `utarray_new`. Later when you're
done with the array, `utarray_free` will free it and all its elements.
Push, pop, etc
~~~~~~~~~~~~~~
The central features of the utarray involve putting elements into it, taking
them out, and iterating over them. There are several <<operations,operations>>
to pick from that deal with either single elements or ranges of elements at a
time. In the examples below we will use only the push operation to insert
elements.
Elements
--------
Support for dynamic arrays of integers or strings is especially easy. These are
best shown by example:
Integers
~~~~~~~~
This example makes a utarray of integers, pushes 0-9 into it, then prints it.
Lastly it frees it.
.Integer elements
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utarray.h"
int main() {
UT_array *nums;
int i, *p;
utarray_new(nums,&ut_int_icd);
for(i=0; i < 10; i++) utarray_push_back(nums,&i);
for(p=(int*)utarray_front(nums);
p!=NULL;
p=(int*)utarray_next(nums,p)) {
printf("%d\n",*p);
}
utarray_free(nums);
return 0;
}
-------------------------------------------------------------------------------
The second argument to `utarray_push_back` is always a 'pointer' to the type
(so a literal cannot be used). So for integers, it is an `int*`.
Strings
~~~~~~~
In this example we make a utarray of strings, push two strings into it, print
it and free it.
.String elements
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utarray.h"
int main() {
UT_array *strs;
char *s, **p;
utarray_new(strs,&ut_str_icd);
s = "hello"; utarray_push_back(strs, &s);
s = "world"; utarray_push_back(strs, &s);
p = NULL;
while ( (p=(char**)utarray_next(strs,p))) {
printf("%s\n",*p);
}
utarray_free(strs);
return 0;
}
-------------------------------------------------------------------------------
In this example, since the element is a `char*`, we pass a pointer to it
(`char**`) as the second argument to `utarray_push_back`. Note that "push" makes
a copy of the source string and pushes that copy into the array.
About UT_icd
~~~~~~~~~~~~
Arrays be made of any type of element, not just integers and strings. The
elements can be basic types or structures. Unless you're dealing with integers
and strings (which use pre-defined `ut_int_icd` and `ut_str_icd`), you'll need
to define a `UT_icd` helper structure. This structure contains everything that
utarray needs to initialize, copy or destruct elements.
typedef struct {
size_t sz;
init_f *init;
ctor_f *copy;
dtor_f *dtor;
} UT_icd;
The three function pointers `init`, `copy`, and `dtor` have these prototypes:
typedef void (ctor_f)(void *dst, const void *src);
typedef void (dtor_f)(void *elt);
typedef void (init_f)(void *elt);
The `sz` is just the size of the element being stored in the array.
The `init` function will be invoked whenever utarray needs to initialize an
empty element. This only happens as a byproduct of `utarray_resize` or
`utarray_extend_back`. If `init` is `NULL`, it defaults to zero filling the
new element using memset.
The `copy` function is used whenever an element is copied into the array.
It is invoked during `utarray_push_back`, `utarray_insert`, `utarray_inserta`,
or `utarray_concat`. If `copy` is `NULL`, it defaults to a bitwise copy using
memcpy.
The `dtor` function is used to clean up an element that is being removed from
the array. It may be invoked due to `utarray_resize`, `utarray_pop_back`,
`utarray_erase`, `utarray_clear`, `utarray_done` or `utarray_free`. If the
elements need no cleanup upon destruction, `dtor` may be `NULL`.
Scalar types
~~~~~~~~~~~~
The next example uses `UT_icd` with all its defaults to make a utarray of
`long` elements. This example pushes two longs, prints them, and frees the
array.
.long elements
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utarray.h"
UT_icd long_icd = {sizeof(long), NULL, NULL, NULL };
int main() {
UT_array *nums;
long l, *p;
utarray_new(nums, &long_icd);
l=1; utarray_push_back(nums, &l);
l=2; utarray_push_back(nums, &l);
p=NULL;
while( (p=(long*)utarray_next(nums,p))) printf("%ld\n", *p);
utarray_free(nums);
return 0;
}
-------------------------------------------------------------------------------
Structures
~~~~~~~~~~
Structures can be used as utarray elements. If the structure requires no
special effort to initialize, copy or destruct, we can use `UT_icd` with all
its defaults. This example shows a structure that consists of two integers. Here
we push two values, print them and free the array.
.Structure (simple)
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utarray.h"
typedef struct {
int a;
int b;
} intpair_t;
UT_icd intpair_icd = {sizeof(intpair_t), NULL, NULL, NULL};
int main() {
UT_array *pairs;
intpair_t ip, *p;
utarray_new(pairs,&intpair_icd);
ip.a=1; ip.b=2; utarray_push_back(pairs, &ip);
ip.a=10; ip.b=20; utarray_push_back(pairs, &ip);
for(p=(intpair_t*)utarray_front(pairs);
p!=NULL;
p=(intpair_t*)utarray_next(pairs,p)) {
printf("%d %d\n", p->a, p->b);
}
utarray_free(pairs);
return 0;
}
-------------------------------------------------------------------------------
The real utility of `UT_icd` is apparent when the elements of the utarray are
structures that require special work to initialize, copy or destruct.
For example, when a structure contains pointers to related memory areas that
need to be copied when the structure is copied (and freed when the structure is
freed), we can use custom `init`, `copy`, and `dtor` members in the `UT_icd`.
Here we take an example of a structure that contains an integer and a string.
When this element is copied (such as when an element is pushed into the array),
we want to "deep copy" the `s` pointer (so the original element and the new
element point to their own copies of `s`). When an element is destructed, we
want to "deep free" its copy of `s`. Lastly, this example is written to work
even if `s` has the value `NULL`.
.Structure (complex)
-------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include "utarray.h"
typedef struct {
int a;
char *s;
} intchar_t;
void intchar_copy(void *_dst, const void *_src) {
intchar_t *dst = (intchar_t*)_dst, *src = (intchar_t*)_src;
dst->a = src->a;
dst->s = src->s ? strdup(src->s) : NULL;
}
void intchar_dtor(void *_elt) {
intchar_t *elt = (intchar_t*)_elt;
if (elt->s) free(elt->s);
}
UT_icd intchar_icd = {sizeof(intchar_t), NULL, intchar_copy, intchar_dtor};
int main() {
UT_array *intchars;
intchar_t ic, *p;
utarray_new(intchars, &intchar_icd);
ic.a=1; ic.s="hello"; utarray_push_back(intchars, &ic);
ic.a=2; ic.s="world"; utarray_push_back(intchars, &ic);
p=NULL;
while( (p=(intchar_t*)utarray_next(intchars,p))) {
printf("%d %s\n", p->a, (p->s ? p->s : "null"));
}
utarray_free(intchars);
return 0;
}
-------------------------------------------------------------------------------
[[operations]]
Reference
---------
This table lists all the utarray operations. These are loosely based on the C++
vector class.
Operations
~~~~~~~~~~
[width="100%",cols="50<m,40<",grid="none",options="none"]
|===============================================================================
| utarray_new(UT_array *a, UT_icd *icd)| allocate a new array
| utarray_free(UT_array *a) | free an allocated array
| utarray_init(UT_array *a,UT_icd *icd)| init an array (non-alloc)
| utarray_done(UT_array *a) | dispose of an array (non-allocd)
| utarray_reserve(UT_array *a,int n) | ensure space available for 'n' more elements
| utarray_push_back(UT_array *a,void *p) | push element p onto a
| utarray_pop_back(UT_array *a) | pop last element from a
| utarray_extend_back(UT_array *a) | push empty element onto a
| utarray_len(UT_array *a) | get length of a
| utarray_eltptr(UT_array *a,int j) | get pointer of element from index
| utarray_eltidx(UT_array *a,void *e) | get index of element from pointer
| utarray_insert(UT_array *a,void *p, int j) | insert element p to index j
| utarray_inserta(UT_array *a,UT_array *w, int j) | insert array w into array a at index j
| utarray_resize(UT_array *dst,int num) | extend or shrink array to num elements
| utarray_concat(UT_array *dst,UT_array *src) | copy src to end of dst array
| utarray_erase(UT_array *a,int pos,int len) | remove len elements from a[pos]..a[pos+len-1]
| utarray_clear(UT_array *a) | clear all elements from a, setting its length to zero
| utarray_sort(UT_array *a,cmpfcn *cmp) | sort elements of a using comparison function
| utarray_find(UT_array *a,void *v, cmpfcn *cmp) | find element v in utarray (must be sorted)
| utarray_front(UT_array *a) | get first element of a
| utarray_next(UT_array *a,void *e) | get element of a following e (front if e is NULL)
| utarray_prev(UT_array *a,void *e) | get element of a before e (back if e is NULL)
| utarray_back(UT_array *a) | get last element of a
|===============================================================================
Notes
~~~~~
1. `utarray_new` and `utarray_free` are used to allocate a new array and free it,
while `utarray_init` and `utarray_done` can be used if the UT_array is already
allocated and just needs to be initialized or have its internal resources
freed.
2. `utarray_reserve` takes the "delta" of elements to reserve, not the total
desired capacity of the array. This differs from the C++ STL "reserve" notion.
3. `utarray_sort` expects a comparison function having the usual `strcmp`-like
convention where it accepts two elements (a and b) and returns a negative
value if a precedes b, 0 if a and b sort equally, and positive if b precedes a.
This is an example of a comparison function:
int intsort(const void *a, const void *b) {
int _a = *(const int *)a;
int _b = *(const int *)b;
return (_a < _b) ? -1 : (_a > _b);
}
4. `utarray_find` uses a binary search to locate an element having a certain value
according to the given comparison function. The utarray must be first sorted
using the same comparison function. An example of using `utarray_find` with
a utarray of strings is included in `tests/test61.c`.
5. A 'pointer' to a particular element (obtained using `utarray_eltptr` or
`utarray_front`, `utarray_next`, `utarray_prev`, `utarray_back`) becomes invalid whenever
another element is inserted into the utarray. This is because the internal
memory management may need to `realloc` the element storage to a new address.
For this reason, it's usually better to refer to an element by its integer
'index' in code whose duration may include element insertion.
6. To override the default out-of-memory handling behavior (which calls `exit(-1)`),
override the `utarray_oom()` macro before including `utarray.h`.
For example,
#define utarray_oom() do { longjmp(error_handling_location); } while (0)
...
#include "utarray.h"
// vim: set nowrap syntax=asciidoc:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,288 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="118.44112"
height="22.655222"
id="svg2018"
sodipodi:version="0.32"
inkscape:version="0.44"
version="1.0"
sodipodi:docbase="/home/thanson/code/uthash/trunk/doc/html/img"
sodipodi:docname="uthash-mini.svg">
<defs
id="defs3">
<linearGradient
inkscape:collect="always"
id="linearGradient3964">
<stop
style="stop-color:#00eb00;stop-opacity:1;"
offset="0"
id="stop3966" />
<stop
style="stop-color:#00eb00;stop-opacity:0;"
offset="1"
id="stop3968" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3964"
id="radialGradient3996"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.237347,0,36.5688)"
cx="176.99219"
cy="47.949429"
fx="176.99219"
fy="47.949429"
r="78.257812" />
<linearGradient
id="linearGradient12743">
<stop
style="stop-color:#99e1fa;stop-opacity:1;"
offset="0"
id="stop12745" />
<stop
id="stop12753"
offset="0"
style="stop-color:#99e1fa;stop-opacity:0.49803922;" />
<stop
style="stop-color:#99e1fa;stop-opacity:0;"
offset="1"
id="stop12747" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient12743"
id="radialGradient12751"
cx="165.91866"
cy="45.584854"
fx="165.91866"
fy="45.584854"
r="56.51194"
gradientTransform="matrix(0.268675,0,0,0.16215,17.28599,40.67469)"
gradientUnits="userSpaceOnUse" />
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send"
style="overflow:visible">
<path
id="path7749"
d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z "
style="fill-rule:evenodd;stroke:black;stroke-width:1pt;marker-start:none"
transform="scale(-0.2,-0.2)" />
</marker>
<marker
inkscape:stockid="StopM"
orient="auto"
refY="0"
refX="0"
id="StopM"
style="overflow:visible">
<path
id="path7651"
d="M 0,5.65 L 0,-5.65"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1pt"
transform="scale(0.4,0.4)" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.2"
inkscape:cx="160"
inkscape:cy="90"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:window-width="916"
inkscape:window-height="626"
inkscape:window-x="5"
inkscape:window-y="73" />
<metadata
id="metadata2022">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-17.9166,-36.67108)">
<rect
style="opacity:1;fill:#393be9;fill-opacity:1;stroke:#f18a00;stroke-width:1.51941979;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3981"
width="116.92171"
height="21.135801"
x="18.67631"
y="37.430794"
rx="7.8295798"
ry="5.3735089"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<flowRoot
transform="matrix(0.449676,0,0,0.449676,-20.8252,25.84477)"
style="font-size:47.99999619px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#faf599;fill-opacity:1;stroke:#f3bf33;stroke-opacity:1;font-family:Bitstream Vera Sans"
id="flowRoot3988"
xml:space="preserve"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><flowRegion
style="fill:url(#radialGradient3996);fill-opacity:1"
id="flowRegion3990"><rect
style="font-size:47.99999619px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#faf599;fill-opacity:1;stroke:#f3bf33;stroke-opacity:1;font-family:Bitstream Vera Sans"
y="18"
x="94.666664"
height="61.333332"
width="321.33334"
id="rect3992" /></flowRegion><flowPara
id="flowPara7831">ut hash</flowPara></flowRoot> <rect
style="opacity:1;fill:url(#radialGradient12751);fill-opacity:1;stroke:none;stroke-width:2.82532263;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect10995"
width="30.366741"
height="18.326834"
x="46.68087"
y="38.902855"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<g
id="g7808"
transform="matrix(0.217052,0,0,0.217052,-20.55641,38.41883)"
inkscape:export-filename="/home/thanson/code/uthash/doc/html/img/logo.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<rect
y="37.730064"
x="382.39673"
height="18.405188"
width="23.206543"
id="rect4882"
style="opacity:1;fill:#9be5ea;fill-opacity:1;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:#d48c21;fill-opacity:0.97777776;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4886"
width="23.206543"
height="18.405188"
x="416.39673"
y="37.730064" />
<path
inkscape:connector-type="polyline"
id="path4890"
d="M 372.60327,46.932658 L 381.39673,46.932658"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path4892"
d="M 406.60327,46.932658 L 415.39673,46.932658"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="9.7300644"
x="348.39673"
height="18.405188"
width="23.206543"
id="rect4896"
style="opacity:1;fill:#79c71a;fill-opacity:1;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
style="opacity:1;fill:#f5e1a2;fill-opacity:1;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4898"
width="23.206543"
height="18.405188"
x="382.39673"
y="9.7300644" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 372.60327,18.932658 L 381.39673,18.932658"
id="path4902"
inkscape:connector-type="polyline" />
<rect
y="14.207111"
x="318.45328"
height="10.1194"
width="10.1194"
id="rect4906"
style="opacity:1;fill:#1336e6;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path5789"
d="M 328.57268,19.220474 L 347.39673,19.048081"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 328.57268,19.220474 L 347.39673,19.048081"
id="path5795"
inkscape:connector-type="polyline" />
<rect
y="37.789951"
x="348.20978"
height="18.405188"
width="23.206543"
id="rect5803"
style="opacity:1;fill:#e5e5e5;fill-opacity:1;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
y="42.267002"
x="318.26633"
height="10.1194"
width="10.1194"
id="rect5805"
style="opacity:1;fill:#1336e6;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 328.38572,47.280365 L 347.20977,47.107972"
id="path5807"
inkscape:connector-type="polyline" />
<rect
style="opacity:1;fill:#ddf9ed;fill-opacity:1;stroke:black;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5809"
width="23.206543"
height="18.405188"
x="348.20978"
y="63.720913" />
<rect
style="opacity:1;fill:#1336e6;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5811"
width="10.1194"
height="10.1194"
x="318.26633"
y="68.197968" />
<path
inkscape:connector-type="polyline"
id="path5813"
d="M 328.38572,73.211328 L 347.20977,73.038935"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path5833"
d="M 323.47927,24.326511 L 323.35974,42.267002"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#2f29df;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-type="polyline"
id="path5835"
d="M 323.32603,52.386402 L 323.32603,68.197968"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#2f29df;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path6716"
d="M 429.08836,47.11641 L 394.37307,18.527349 L 394.37307,49.158485 L 359.65778,18.527349 L 359.65778,50.179523 L 359.65778,75.70547"
style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#f3bf33;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;marker-start:url(#StopM);marker-end:url(#Arrow1Send);stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,293 +0,0 @@
utlist: linked list macros for C structures
===========================================
Troy D. Hanson <tdh@tkhanson.net>
v2.3.0, February 2021
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
Introduction
------------
A set of general-purpose 'linked list' macros for C structures are included with
uthash in `utlist.h`. To use these macros in your own C program, just
copy `utlist.h` into your source directory and use it in your programs.
#include "utlist.h"
These macros support the basic linked list operations: adding and deleting
elements, sorting them and iterating over them.
Download
~~~~~~~~
To download the `utlist.h` header file,
follow the links on https://github.com/troydhanson/uthash to clone uthash or get a zip file,
then look in the src/ sub-directory.
BSD licensed
~~~~~~~~~~~~
This software is made available under the
link:license.html[revised BSD license].
It is free and open source.
Platforms
~~~~~~~~~
The 'utlist' macros have been tested on:
* Linux,
* Mac OS X, and
* Windows, using Visual Studio 2008, Visual Studio 2010, or Cygwin/MinGW.
Using utlist
------------
Types of lists
~~~~~~~~~~~~~~
Three types of linked lists are supported:
- *singly-linked* lists,
- *doubly-linked* lists, and
- *circular, doubly-linked* lists
Efficiency
^^^^^^^^^^
Prepending elements::
Constant-time on all list types.
Appending::
'O(n)' on singly-linked lists; constant-time on doubly-linked list.
(The utlist implementation of the doubly-linked list keeps a tail pointer in
`head->prev` so that append can be done in constant time).
Deleting elements::
'O(n)' on singly-linked lists; constant-time on doubly-linked list.
Sorting::
'O(n log(n))' for all list types.
Insertion in order (for sorted lists)::
'O(n)' for all list types.
Iteration, counting and searching::
'O(n)' for all list types.
List elements
~~~~~~~~~~~~~
You can use any structure with these macros, as long as the structure
contains a `next` pointer. If you want to make a doubly-linked list,
the element also needs to have a `prev` pointer.
typedef struct element {
char *name;
struct element *prev; /* needed for a doubly-linked list only */
struct element *next; /* needed for singly- or doubly-linked lists */
} element;
You can name your structure anything. In the example above it is called `element`.
Within a particular list, all elements must be of the same type.
Flexible prev/next naming
^^^^^^^^^^^^^^^^^^^^^^^^^
You can name your `prev` and `next` pointers something else. If you do, there is
a <<flex_names,family of macros>> that work identically but take these names as
extra arguments.
List head
~~~~~~~~~
The list head is simply a pointer to your element structure. You can name it
anything. *It must be initialized to `NULL`*.
element *head = NULL;
List operations
~~~~~~~~~~~~~~~
The lists support inserting or deleting elements, sorting the elements and
iterating over them.
[width="100%",cols="10<m,10<m,10<m",grid="cols",options="header"]
|===============================================================================
|Singly-linked | Doubly-linked | Circular, doubly-linked
|LL_PREPEND(head,add); | DL_PREPEND(head,add); | CDL_PREPEND(head,add);
|LL_PREPEND_ELEM(head,ref,add); | DL_PREPEND_ELEM(head,ref,add); | CDL_PREPEND_ELEM(head,ref,add);
|LL_APPEND_ELEM(head,ref,add); | DL_APPEND_ELEM(head,ref,add); | CDL_APPEND_ELEM(head,ref,add);
|LL_REPLACE_ELEM(head,del,add); | DL_REPLACE_ELEM(head,del,add); | CDL_REPLACE_ELEM(head,del,add);
|LL_APPEND(head,add); | DL_APPEND(head,add); | CDL_APPEND(head,add);
|LL_INSERT_INORDER(head,add,cmp); | DL_INSERT_INORDER(head,add,cmp); | CDL_INSERT_INORDER(head,add,cmp);
|LL_CONCAT(head1,head2); | DL_CONCAT(head1,head2); |
|LL_DELETE(head,del); | DL_DELETE(head,del); | CDL_DELETE(head,del);
|LL_SORT(head,cmp); | DL_SORT(head,cmp); | CDL_SORT(head,cmp);
|LL_FOREACH(head,elt) {...}| DL_FOREACH(head,elt) {...} | CDL_FOREACH(head,elt) {...}
|LL_FOREACH_SAFE(head,elt,tmp) {...}| DL_FOREACH_SAFE(head,elt,tmp) {...} | CDL_FOREACH_SAFE(head,elt,tmp1,tmp2) {...}
|LL_SEARCH_SCALAR(head,elt,mbr,val);| DL_SEARCH_SCALAR(head,elt,mbr,val); | CDL_SEARCH_SCALAR(head,elt,mbr,val);
|LL_SEARCH(head,elt,like,cmp);| DL_SEARCH(head,elt,like,cmp); | CDL_SEARCH(head,elt,like,cmp);
|LL_LOWER_BOUND(head,elt,like,cmp); | DL_LOWER_BOUND(head,elt,like,cmp); | CDL_LOWER_BOUND(head,elt,like,cmp);
|LL_COUNT(head,elt,count); | DL_COUNT(head,elt,count); | CDL_COUNT(head,elt,count);
|===============================================================================
'Prepend' means to insert an element in front of the existing list head (if any),
changing the list head to the new element. 'Append' means to add an element at the
end of the list, so it becomes the new tail element. 'Concatenate' takes two
properly constructed lists and appends the second list to the first. (Visual
Studio 2008 does not support `LL_CONCAT` and `DL_CONCAT`, but VS2010 is ok.)
To prepend before an arbitrary element instead of the list head, use the
`_PREPEND_ELEM` macro family.
To append after an arbitrary element element instead of the list head, use the
`_APPEND_ELEM` macro family.
To 'replace' an arbitrary list element with another element use the `_REPLACE_ELEM`
family of macros.
The 'sort' operation never moves the elements in memory; rather it only adjusts
the list order by altering the `prev` and `next` pointers in each element. Also
the sort operation can change the list head to point to a new element.
The 'foreach' operation is for easy iteration over the list from the head to the
tail. A usage example is shown below. You can of course just use the `prev` and
`next` pointers directly instead of using the 'foreach' macros.
The 'foreach_safe' operation should be used if you plan to delete any of the list
elements while iterating.
The 'search' operation is a shortcut for iteration in search of a particular
element. It is not any faster than manually iterating and testing each element.
There are two forms: the "scalar" version searches for an element using a
simple equality test on a given structure member, while the general version takes an
element to which all others in the list will be compared using a `cmp` function.
The 'lower_bound' operation finds the first element of the list which is no greater
than the provided `like` element, according to the provided `cmp` function.
The 'lower_bound' operation sets `elt` to a suitable value for passing to
`LL_APPEND_ELEM`; i.e., `elt=NULL` if the proper insertion point is at the front of
the list, and `elt=p` if the proper insertion point is between `p` and `p->next`.
The 'count' operation iterates over the list and increments a supplied counter.
The parameters shown in the table above are explained here:
head::
The list head (a pointer to your list element structure).
add::
A pointer to the list element structure you are adding to the list.
del::
A pointer to the list element structure you are replacing or
deleting from the list.
elt::
A pointer that will be assigned to each list element in succession (see
example) in the case of iteration macros; or, the output pointer from
the search macros.
ref::
Reference element for prepend and append operations that will be
prepended before or appended after.
If `ref` is a pointer with value NULL, the new element will be appended to the
list for _PREPEND_ELEM() operations and prepended for _APPEND_ELEM() operations.
`ref` must be the name of a pointer variable and cannot be literally NULL,
use _PREPEND() and _APPEND() macro family instead.
like::
An element pointer, having the same type as `elt`, for which the search macro
seeks a match (if found, the match is stored in `elt`). A match is determined
by the given `cmp` function.
cmp::
pointer to comparison function which accepts two arguments-- these are
pointers to two element structures to be compared. The comparison function
must return an `int` that is negative, zero, or positive, which specifies
whether the first item should sort before, equal to, or after the second item,
respectively. (In other words, the same convention that is used by `strcmp`).
Note that under Visual Studio 2008 you may need to declare the two arguments
as `void *` and then cast them back to their actual types.
tmp::
A pointer of the same type as `elt`. Used internally. Need not be initialized.
mbr::
In the scalar search macro, the name of a member within the `elt` structure which
will be tested (using `==`) for equality with the value `val`.
val::
In the scalar search macro, specifies the value of (of structure member
`field`) of the element being sought.
count::
integer which will be set to the length of the list
Example
~~~~~~~
This example program reads names from a text file (one name per line), and
appends each name to a doubly-linked list. Then it sorts and prints them.
.A doubly-linked list
--------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utlist.h"
#define BUFLEN 20
typedef struct el {
char bname[BUFLEN];
struct el *next, *prev;
} el;
int namecmp(el *a, el *b) {
return strcmp(a->bname,b->bname);
}
el *head = NULL; /* important- initialize to NULL! */
int main(int argc, char *argv[]) {
el *name, *elt, *tmp, etmp;
char linebuf[BUFLEN];
int count;
FILE *file;
if ( (file = fopen( "test11.dat", "r" )) == NULL ) {
perror("can't open: ");
exit(-1);
}
while (fgets(linebuf,BUFLEN,file) != NULL) {
if ( (name = (el *)malloc(sizeof *name)) == NULL) exit(-1);
strcpy(name->bname, linebuf);
DL_APPEND(head, name);
}
DL_SORT(head, namecmp);
DL_FOREACH(head,elt) printf("%s", elt->bname);
DL_COUNT(head, elt, count);
printf("%d number of elements in list\n", count);
memcpy(&etmp.bname, "WES\n", 5);
DL_SEARCH(head,elt,&etmp,namecmp);
if (elt) printf("found %s\n", elt->bname);
/* now delete each element, use the safe iterator */
DL_FOREACH_SAFE(head,elt,tmp) {
DL_DELETE(head,elt);
free(elt);
}
fclose(file);
return 0;
}
--------------------------------------------------------------------------------
[[flex_names]]
Other names for prev and next
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the `prev` and `next` fields are named something else, a separate group of
macros must be used. These work the same as the regular macros, but take the
field names as extra parameters.
These "flexible field name" macros are shown below. They all end with `2`. Each
operates the same as its counterpart without the `2`, but they take the name of
the `prev` and `next` fields (as applicable) as trailing arguments.
[width="100%",cols="10<m,10<m,10<m",grid="cols",options="header"]
|===============================================================================
|Singly-linked | Doubly-linked | Circular, doubly-linked
|LL_PREPEND2(head,add,next); | DL_PREPEND2(head,add,prev,next); | CDL_PREPEND2(head,add,prev,next);
|LL_PREPEND_ELEM2(head,ref,add,next); | DL_PREPEND_ELEM2(head,ref,add,prev,next); | CDL_PREPEND_ELEM2(head,ref,add,prev,next);
|LL_APPEND_ELEM2(head,ref,add,next); | DL_APPEND_ELEM2(head,ref,add,prev,next); | CDL_APPEND_ELEM2(head,ref,add,prev,next);
|LL_REPLACE_ELEM2(head,del,add,next); | DL_REPLACE_ELEM2(head,del,add,prev,next); | CDL_REPLACE_ELEM2(head,del,add,prev,next);
|LL_APPEND2(head,add,next); | DL_APPEND2(head,add,prev,next); | CDL_APPEND2(head,add,prev,next);
|LL_INSERT_INORDER2(head,add,cmp,next); | DL_INSERT_INORDER2(head,add,cmp,prev,next); | CDL_INSERT_INORDER2(head,add,cmp,prev,next);
|LL_CONCAT2(head1,head2,next); | DL_CONCAT2(head1,head2,prev,next); |
|LL_DELETE2(head,del,next); | DL_DELETE2(head,del,prev,next); | CDL_DELETE2(head,del,prev,next);
|LL_SORT2(head,cmp,next); | DL_SORT2(head,cmp,prev,next); | CDL_SORT2(head,cmp,prev,next);
|LL_FOREACH2(head,elt,next) {...} | DL_FOREACH2(head,elt,next) {...} | CDL_FOREACH2(head,elt,next) {...}
|LL_FOREACH_SAFE2(head,elt,tmp,next) {...} | DL_FOREACH_SAFE2(head,elt,tmp,next) {...} | CDL_FOREACH_SAFE2(head,elt,tmp1,tmp2,prev,next) {...}
|LL_SEARCH_SCALAR2(head,elt,mbr,val,next); | DL_SEARCH_SCALAR2(head,elt,mbr,val,next); | CDL_SEARCH_SCALAR2(head,elt,mbr,val,next);
|LL_SEARCH2(head,elt,like,cmp,next); | DL_SEARCH2(head,elt,like,cmp,next); | CDL_SEARCH2(head,elt,like,cmp,next);
|LL_LOWER_BOUND2(head,elt,like,cmp,next); | DL_LOWER_BOUND2(head,elt,like,cmp,next); | CDL_LOWER_BOUND2(head,elt,like,cmp,next);
|LL_COUNT2(head,elt,count,next); | DL_COUNT2(head,elt,count,next); | CDL_COUNT2(head,elt,count,next);
|===============================================================================
// vim: set tw=80 wm=2 syntax=asciidoc:

View File

@@ -1,374 +0,0 @@
utringbuffer: dynamic ring-buffer macros for C
==============================================
Arthur O'Dwyer <arthur.j.odwyer@gmail.com>
v2.3.0, February 2021
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
Introduction
------------
The functions in `utringbuffer.h` are based on the general-purpose array macros
provided in `utarray.h`, so before reading this page you should read
link:utarray.html[that page] first.
To use these macros in your own C program, copy both `utarray.h` and `utringbuffer.h`
into your source directory and use `utringbuffer.h` in your program.
#include "utringbuffer.h"
The provided <<operations,operations>> are based loosely on the C++ STL vector methods.
The ring-buffer data type supports construction (with a specified capacity),
destruction, iteration, and push, but not pop; once the ring-buffer reaches full
capacity, pushing a new element automatically pops and destroys the oldest element.
The elements contained in the ring-buffer can be any simple datatype or structure.
Internally the ring-buffer contains a pre-allocated memory region into which the
elements are copied, starting at position 0. When the ring-buffer reaches full
capacity, the next element to be pushed is pushed at position 0, overwriting the
oldest element, and the internal index representing the "start" of the ring-buffer
is incremented. A ring-buffer, once full, can never become un-full.
Download
~~~~~~~~
To download the `utringbuffer.h` header file,
follow the links on https://github.com/troydhanson/uthash to clone uthash or get a zip file,
then look in the src/ sub-directory.
BSD licensed
~~~~~~~~~~~~
This software is made available under the
link:license.html[revised BSD license].
It is free and open source.
Platforms
~~~~~~~~~
The 'utringbuffer' macros have been tested on:
* Linux,
* Mac OS X,
* Windows, using Visual Studio 2008 and Visual Studio 2010
Usage
-----
Declaration
~~~~~~~~~~~
The ring-buffer itself has the data type `UT_ringbuffer`, regardless of the type of
elements to be stored in it. It is declared like,
UT_ringbuffer *history;
New and free
~~~~~~~~~~~~
The next step is to create the ring-buffer using `utringbuffer_new`. Later when you're
done with the ring-buffer, `utringbuffer_free` will free it and all its elements.
Push, etc
~~~~~~~~~
The central features of the ring-buffer involve putting elements into it
and iterating over them. There are several <<operations,operations>>
that deal with either single elements or ranges of elements at a
time. In the examples below we will use only the push operation to insert
elements.
Elements
--------
Support for dynamic arrays of integers or strings is especially easy. These are
best shown by example:
Integers
~~~~~~~~
This example makes a ring-buffer of integers, pushes 0-9 into it, then prints it
two different ways. Lastly it frees it.
.Integer elements
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utringbuffer.h"
int main() {
UT_ringbuffer *history;
int i, *p;
utringbuffer_new(history, 7, &ut_int_icd);
for(i=0; i < 10; i++) utringbuffer_push_back(history, &i);
for (p = (int*)utringbuffer_front(history);
p != NULL;
p = (int*)utringbuffer_next(history, p)) {
printf("%d\n", *p); /* prints "3 4 5 6 7 8 9" */
}
for (i=0; i < utringbuffer_len(history); i++) {
p = utringbuffer_eltptr(history, i);
printf("%d\n", *p); /* prints "3 4 5 6 7 8 9" */
}
utringbuffer_free(history);
return 0;
}
-------------------------------------------------------------------------------
The second argument to `utringbuffer_push_back` is always a 'pointer' to the type
(so a literal cannot be used). So for integers, it is an `int*`.
Strings
~~~~~~~
In this example we make a ring-buffer of strings, push two strings into it, print
it and free it.
.String elements
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utringbuffer.h"
int main() {
UT_ringbuffer *strs;
char *s, **p;
utringbuffer_new(strs, 7, &ut_str_icd);
s = "hello"; utringbuffer_push_back(strs, &s);
s = "world"; utringbuffer_push_back(strs, &s);
p = NULL;
while ( (p=(char**)utringbuffer_next(strs,p))) {
printf("%s\n",*p);
}
utringbuffer_free(strs);
return 0;
}
-------------------------------------------------------------------------------
In this example, since the element is a `char*`, we pass a pointer to it
(`char**`) as the second argument to `utringbuffer_push_back`. Note that "push" makes
a copy of the source string and pushes that copy into the array.
About UT_icd
~~~~~~~~~~~~
Arrays can be made of any type of element, not just integers and strings. The
elements can be basic types or structures. Unless you're dealing with integers
and strings (which use pre-defined `ut_int_icd` and `ut_str_icd`), you'll need
to define a `UT_icd` helper structure. This structure contains everything that
utringbuffer (or utarray) needs to initialize, copy or destruct elements.
typedef struct {
size_t sz;
init_f *init;
ctor_f *copy;
dtor_f *dtor;
} UT_icd;
The three function pointers `init`, `copy`, and `dtor` have these prototypes:
typedef void (ctor_f)(void *dst, const void *src);
typedef void (dtor_f)(void *elt);
typedef void (init_f)(void *elt);
The `sz` is just the size of the element being stored in the array.
The `init` function is used by utarray but is never used by utringbuffer;
you may safely set it to any value you want.
The `copy` function is used whenever an element is copied into the buffer.
It is invoked during `utringbuffer_push_back`.
If `copy` is `NULL`, it defaults to a bitwise copy using memcpy.
The `dtor` function is used to clean up an element that is being removed from
the buffer. It may be invoked due to `utringbuffer_push_back` (on the oldest
element in the buffer), `utringbuffer_clear`, `utringbuffer_done`, or
`utringbuffer_free`.
If the elements need no cleanup upon destruction, `dtor` may be `NULL`.
Scalar types
~~~~~~~~~~~~
The next example uses `UT_icd` with all its defaults to make a ring-buffer of
`long` elements. This example pushes two longs into a buffer of capacity 1,
prints the contents of the buffer (which is to say, the most recent value
pushed), and then frees the buffer.
.long elements
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utringbuffer.h"
UT_icd long_icd = {sizeof(long), NULL, NULL, NULL };
int main() {
UT_ringbuffer *nums;
long l, *p;
utringbuffer_new(nums, 1, &long_icd);
l=1; utringbuffer_push_back(nums, &l);
l=2; utringbuffer_push_back(nums, &l);
p=NULL;
while((p = (long*)utringbuffer_next(nums,p))) printf("%ld\n", *p);
utringbuffer_free(nums);
return 0;
}
-------------------------------------------------------------------------------
Structures
~~~~~~~~~~
Structures can be used as utringbuffer elements. If the structure requires no
special effort to initialize, copy or destruct, we can use `UT_icd` with all
its defaults. This example shows a structure that consists of two integers. Here
we push two values, print them and free the buffer.
.Structure (simple)
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utringbuffer.h"
typedef struct {
int a;
int b;
} intpair_t;
UT_icd intpair_icd = {sizeof(intpair_t), NULL, NULL, NULL};
int main() {
UT_ringbuffer *pairs;
intpair_t ip, *p;
utringbuffer_new(pairs, 7, &intpair_icd);
ip.a=1; ip.b=2; utringbuffer_push_back(pairs, &ip);
ip.a=10; ip.b=20; utringbuffer_push_back(pairs, &ip);
for(p=(intpair_t*)utringbuffer_front(pairs);
p!=NULL;
p=(intpair_t*)utringbuffer_next(pairs,p)) {
printf("%d %d\n", p->a, p->b);
}
utringbuffer_free(pairs);
return 0;
}
-------------------------------------------------------------------------------
The real utility of `UT_icd` is apparent when the elements stored in the
ring-buffer are structures that require special work to initialize, copy or
destruct.
For example, when a structure contains pointers to related memory areas that
need to be copied when the structure is copied (and freed when the structure is
freed), we can use custom `init`, `copy`, and `dtor` members in the `UT_icd`.
Here we take an example of a structure that contains an integer and a string.
When this element is copied (such as when an element is pushed),
we want to "deep copy" the `s` pointer (so the original element and the new
element point to their own copies of `s`). When an element is destructed, we
want to "deep free" its copy of `s`. Lastly, this example is written to work
even if `s` has the value `NULL`.
.Structure (complex)
-------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include "utringbuffer.h"
typedef struct {
int a;
char *s;
} intchar_t;
void intchar_copy(void *_dst, const void *_src) {
intchar_t *dst = (intchar_t*)_dst, *src = (intchar_t*)_src;
dst->a = src->a;
dst->s = src->s ? strdup(src->s) : NULL;
}
void intchar_dtor(void *_elt) {
intchar_t *elt = (intchar_t*)_elt;
free(elt->s);
}
UT_icd intchar_icd = {sizeof(intchar_t), NULL, intchar_copy, intchar_dtor};
int main() {
UT_ringbuffer *intchars;
intchar_t ic, *p;
utringbuffer_new(intchars, 2, &intchar_icd);
ic.a=1; ic.s="hello"; utringbuffer_push_back(intchars, &ic);
ic.a=2; ic.s="world"; utringbuffer_push_back(intchars, &ic);
ic.a=3; ic.s="peace"; utringbuffer_push_back(intchars, &ic);
p=NULL;
while( (p=(intchar_t*)utringbuffer_next(intchars,p))) {
printf("%d %s\n", p->a, (p->s ? p->s : "null"));
/* prints "2 world 3 peace" */
}
utringbuffer_free(intchars);
return 0;
}
-------------------------------------------------------------------------------
[[operations]]
Reference
---------
This table lists all the utringbuffer operations. These are loosely based on the C++
vector class.
Operations
~~~~~~~~~~
[width="100%",cols="50<m,40<",grid="none",options="none"]
|===============================================================================
| utringbuffer_new(UT_ringbuffer *a, int n, UT_icd *icd) | allocate a new ringbuffer
| utringbuffer_free(UT_ringbuffer *a) | free an allocated ringbuffer
| utringbuffer_init(UT_ringbuffer *a, int n, UT_icd *icd) | init a ringbuffer (non-alloc)
| utringbuffer_done(UT_ringbuffer *a) | dispose of a ringbuffer (non-alloc)
| utringbuffer_clear(UT_ringbuffer *a) | clear all elements from a, making it empty
| utringbuffer_push_back(UT_ringbuffer *a, element *p) | push element p onto a
| utringbuffer_len(UT_ringbuffer *a) | get length of a
| utringbuffer_empty(UT_ringbuffer *a) | get whether a is empty
| utringbuffer_full(UT_ringbuffer *a) | get whether a is full
| utringbuffer_eltptr(UT_ringbuffer *a, int j) | get pointer of element from index
| utringbuffer_eltidx(UT_ringbuffer *a, element *e) | get index of element from pointer
| utringbuffer_front(UT_ringbuffer *a) | get oldest element of a
| utringbuffer_next(UT_ringbuffer *a, element *e) | get element of a following e (front if e is NULL)
| utringbuffer_prev(UT_ringbuffer *a, element *e) | get element of a before e (back if e is NULL)
| utringbuffer_back(UT_ringbuffer *a) | get newest element of a
|===============================================================================
Notes
~~~~~
1. `utringbuffer_new` and `utringbuffer_free` are used to allocate a new ring-buffer
and to free it,
while `utringbuffer_init` and `utringbuffer_done` can be used if the UT_ringbuffer
is already allocated and just needs to be initialized or have its internal resources
freed.
2. Both `utringbuffer_new` and `utringbuffer_init` take a second parameter `n` indicating
the capacity of the ring-buffer, that is, the size at which the ring-buffer is considered
"full" and begins to overwrite old elements with newly pushed ones.
3. Once a ring-buffer has become full, it will never again become un-full except by
means of `utringbuffer_clear`. There is no way to "pop" a single old item from the
front of the ring-buffer. You can simulate this ability by maintaining a separate
integer count of the number of "logically popped elements", and starting your iteration
with `utringbuffer_eltptr(a, popped_count)` instead of with `utringbuffer_front(a)`.
4. Pointers to elements (obtained using `utringbuffer_eltptr`, `utringbuffer_front`,
`utringbuffer_next`, etc.) are not generally invalidated by `utringbuffer_push_back`,
because utringbuffer does not perform reallocation; however, a pointer to the oldest
element may suddenly turn into a pointer to the 'newest' element if
`utringbuffer_push_back` is called while the buffer is full.
5. The elements of a ring-buffer are stored in contiguous memory, but once the ring-buffer
has become full, it is no longer true that the elements are contiguously in order from
oldest to newest; i.e., `(element *)utringbuffer_front(a) + utringbuffer_len(a)-1` is
not generally equal to `(element *)utringbuffer_back(a)`.
// vim: set nowrap syntax=asciidoc:

View File

@@ -1,158 +0,0 @@
utstack: intrusive stack macros for C
=====================================
Arthur O'Dwyer <arthur.j.odwyer@gmail.com>
v2.3.0, February 2021
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
Introduction
------------
A set of very simple stack macros for C structures are included with
uthash in `utstack.h`. To use these macros in your own C program, just
copy `utstack.h` into your source directory and use it in your programs.
#include "utstack.h"
These macros support the basic operations of a stack, implemented as
an intrusive linked list. A stack supports the "push", "pop", and "count"
operations, as well as the trivial operation of getting the top element
of the stack.
Download
~~~~~~~~
To download the `utstack.h` header file,
follow the links on https://github.com/troydhanson/uthash to clone uthash or get a zip file,
then look in the src/ sub-directory.
BSD licensed
~~~~~~~~~~~~
This software is made available under the
link:license.html[revised BSD license].
It is free and open source.
Platforms
~~~~~~~~~
The 'utstack' macros have been tested on:
* Linux,
* Mac OS X,
* Windows, using Visual Studio 2008 and Visual Studio 2010
Usage
-----
Stack (list) head
~~~~~~~~~~~~~~~~~
The stack head is simply a pointer to your element structure. You can name it
anything. *It must be initialized to `NULL`*. It doubles as a pointer to the
top element of your stack.
element *stack = NULL;
Stack operations
~~~~~~~~~~~~~~~
The only operations on a stack are O(1) pushing, O(1) popping, and
O(n) counting the number of elements on the stack. None of the provided
macros permit directly accessing stack elements other than the top element.
To increase the readability of your code, you can use the macro
`STACK_EMPTY(head)` as a more readable alternative to `head == NULL`,
and `STACK_TOP(head)` as a more readable alternative to `head`.
[width="100%",cols="50<m,40<",grid="none",options="none"]
|===============================================================================
|STACK_PUSH(stack,add); | push `add` onto `stack`
|STACK_POP(stack,elt); | pop `stack` and save previous top as `elt`
|STACK_COUNT(stack,tmp,count); | store number of elements into `count`
|STACK_TOP(stack) | return `stack`
|STACK_EMPTY(stack) | return `stack == NULL`
|===============================================================================
The parameters shown in the table above are explained here:
stack::
The stack head (a pointer to your element structure).
add::
A pointer to the element structure you are adding to the stack.
elt::
A pointer that will be assigned the address of the popped element. Need not be initialized.
tmp::
A pointer of the same type as `elt`. Used internally. Need not be initialized.
count::
An integer that will be assigned the size of the stack. Need not be initialized.
Example
~~~~~~~
This example program reads names from a text file (one name per line), and
pushes each name on the stack; then pops and prints them in reverse order.
.A stack of names
--------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utstack.h"
#define BUFLEN 20
typedef struct el {
char bname[BUFLEN];
struct el *next;
} el;
el *head = NULL; /* important- initialize to NULL! */
int main(int argc, char *argv[]) {
el *elt, *tmp;
char linebuf[sizeof el->bname];
int count;
FILE *file = fopen("test11.dat", "r");
if (file == NULL) {
perror("can't open: ");
exit(-1);
}
while (fgets(linebuf, sizeof linebuf, file) != NULL) {
el *name = malloc(sizeof *name);
if (name == NULL) exit(-1);
strcpy(name->bname, linebuf);
STACK_PUSH(head, name);
}
fclose(file);
STACK_COUNT(head, elt, count);
printf("%d elements were read into the stack\n", count);
/* now pop, print, and delete each element */
while (!STACK_EMPTY(head)) {
printf("%s\n", STACK_TOP(head)->bname);
STACK_POP(head, elt);
free(elt);
}
return 0;
}
--------------------------------------------------------------------------------
[[flex_names]]
Other names for next
~~~~~~~~~~~~~~~~~~~~
If the element structure's `next` field is named something else, a separate group
of macros must be used. These work the same as the regular macros, but take the
field name as an extra parameter.
These "flexible field name" macros are shown below. They all end with `2`. Each
operates the same as its counterpart without the `2`, but they take the name of
the `next` field as a trailing argument.
[width="100%",cols="50<m,40<",grid="none",options="none"]
|===============================================================================
|STACK_PUSH2(stack,add,next); | push `add` onto `stack`
|STACK_POP2(stack,elt,next); | pop `stack` and save previous top as `elt`
|STACK_COUNT2(stack,tmp,count,next); | store number of elements into `count`
|===============================================================================
// vim: set nowrap syntax=asciidoc:

View File

@@ -1,239 +0,0 @@
utstring: dynamic string macros for C
=====================================
Troy D. Hanson <tdh@tkhanson.net>
v2.3.0, February 2021
Here's a link back to the https://github.com/troydhanson/uthash[GitHub project page].
Introduction
------------
A set of basic dynamic string macros for C programs are included with
uthash in `utstring.h`. To use these in your own C program, just copy
`utstring.h` into your source directory and use it in your programs.
#include "utstring.h"
The dynamic string supports operations such as inserting data, concatenation,
getting the length and content, substring search, and clear. It's ok to put
binary data into a utstring too. The string <<operations,operations>> are
listed below.
Some utstring operations are implemented as functions rather than macros.
Download
~~~~~~~~
To download the `utstring.h` header file,
follow the links on https://github.com/troydhanson/uthash to clone uthash or get a zip file,
then look in the src/ sub-directory.
BSD licensed
~~~~~~~~~~~~
This software is made available under the
link:license.html[revised BSD license].
It is free and open source.
Platforms
~~~~~~~~~
The 'utstring' macros have been tested on:
* Linux,
* Windows, using Visual Studio 2008 and Visual Studio 2010
Usage
-----
Declaration
~~~~~~~~~~~
The dynamic string itself has the data type `UT_string`. It is declared like,
UT_string *str;
New and free
~~~~~~~~~~~~
The next step is to create the string using `utstring_new`. Later when you're
done with it, `utstring_free` will free it and all its content.
Manipulation
~~~~~~~~~~~~
The `utstring_printf` or `utstring_bincpy` operations insert (copy) data into
the string. To concatenate one utstring to another, use `utstring_concat`. To
clear the content of the string, use `utstring_clear`. The length of the string
is available from `utstring_len`, and its content from `utstring_body`. This
evaluates to a `char*`. The buffer it points to is always null-terminated.
So, it can be used directly with external functions that expect a string.
This automatic null terminator is not counted in the length of the string.
Samples
~~~~~~~
These examples show how to use utstring.
.Sample 1
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utstring.h"
int main() {
UT_string *s;
utstring_new(s);
utstring_printf(s, "hello world!" );
printf("%s\n", utstring_body(s));
utstring_free(s);
return 0;
}
-------------------------------------------------------------------------------
The next example demonstrates that `utstring_printf` 'appends' to the string.
It also shows concatenation.
.Sample 2
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utstring.h"
int main() {
UT_string *s, *t;
utstring_new(s);
utstring_new(t);
utstring_printf(s, "hello " );
utstring_printf(s, "world " );
utstring_printf(t, "hi " );
utstring_printf(t, "there " );
utstring_concat(s, t);
printf("length: %u\n", utstring_len(s));
printf("%s\n", utstring_body(s));
utstring_free(s);
utstring_free(t);
return 0;
}
-------------------------------------------------------------------------------
The next example shows how binary data can be inserted into the string. It also
clears the string and prints new data into it.
.Sample 3
-------------------------------------------------------------------------------
#include <stdio.h>
#include "utstring.h"
int main() {
UT_string *s;
char binary[] = "\xff\xff";
utstring_new(s);
utstring_bincpy(s, binary, sizeof(binary));
printf("length is %u\n", utstring_len(s));
utstring_clear(s);
utstring_printf(s,"number %d", 10);
printf("%s\n", utstring_body(s));
utstring_free(s);
return 0;
}
-------------------------------------------------------------------------------
[[operations]]
Reference
---------
These are the utstring operations.
Operations
~~~~~~~~~~
[width="100%",cols="50<m,40<",grid="none",options="none"]
|===============================================================================
| utstring_new(s) | allocate a new utstring
| utstring_renew(s) | allocate a new utstring (if s is `NULL`) otherwise clears it
| utstring_free(s) | free an allocated utstring
| utstring_init(s) | init a utstring (non-alloc)
| utstring_done(s) | dispose of a utstring (non-alloc)
| utstring_printf(s,fmt,...) | printf into a utstring (appends)
| utstring_bincpy(s,bin,len) | insert binary data of length len (appends)
| utstring_concat(dst,src) | concatenate src utstring to end of dst utstring
| utstring_clear(s) | clear the content of s (setting its length to 0)
| utstring_len(s) | obtain the length of s as an unsigned integer
| utstring_body(s) | get `char*` to body of s (buffer is always null-terminated)
| utstring_find(s,pos,str,len) | forward search from pos for a substring
| utstring_findR(s,pos,str,len) | reverse search from pos for a substring
|===============================================================================
New/free vs. init/done
~~~~~~~~~~~~~~~~~~~~~~
Use `utstring_new` and `utstring_free` to allocate a new string or free it. If
the UT_string is statically allocated, use `utstring_init` and `utstring_done`
to initialize or free its internal memory.
Substring search
~~~~~~~~~~~~~~~~
Use `utstring_find` and `utstring_findR` to search for a substring in a utstring.
It comes in forward and reverse varieties. The reverse search scans from the end of
the string backward. These take a position to start searching from, measured from 0
(the start of the utstring). A negative position is counted from the end of
the string, so, -1 is the last position. Note that in the reverse search, the
initial position anchors to the 'end' of the substring being searched for;
e.g., the 't' in 'cat'. The return value always refers to the offset where the
substring 'starts' in the utstring. When no substring match is found, -1 is
returned.
For example if a utstring called `s` contains:
ABC ABCDAB ABCDABCDABDE
Then these forward and reverse substring searches for `ABC` produce these results:
utstring_find( s, -9, "ABC", 3 ) = 15
utstring_find( s, 3, "ABC", 3 ) = 4
utstring_find( s, 16, "ABC", 3 ) = -1
utstring_findR( s, -9, "ABC", 3 ) = 11
utstring_findR( s, 12, "ABC", 3 ) = 4
utstring_findR( s, 2, "ABC", 3 ) = 0
"Multiple use" substring search
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The preceding examples show "single use" versions of substring matching, where
the internal Knuth-Morris-Pratt (KMP) table is internally built and then freed
after the search. If your program needs to run many searches for a given
substring, it is more efficient to save the KMP table and reuse it.
To reuse the KMP table, build it manually and then pass it into the internal
search functions. The functions involved are:
_utstring_BuildTable (build the KMP table for a forward search)
_utstring_BuildTableR (build the KMP table for a reverse search)
_utstring_find (forward search using a prebuilt KMP table)
_utstring_findR (reverse search using a prebuilt KMP table)
This is an example of building a forward KMP table for the substring "ABC", and
then using it in a search:
long *KPM_TABLE, offset;
KPM_TABLE = (long *)malloc( sizeof(long) * (strlen("ABC")) + 1));
_utstring_BuildTable("ABC", 3, KPM_TABLE);
offset = _utstring_find(utstring_body(s), utstring_len(s), "ABC", 3, KPM_TABLE );
free(KPM_TABLE);
Note that the internal `_utstring_find` has the length of the UT_string as its
second argument, rather than the start position. You can emulate the position
parameter by adding to the string start address and subtracting from its length.
Notes
~~~~~
1. To override the default out-of-memory handling behavior (which calls `exit(-1)`),
override the `utstring_oom()` macro before including `utstring.h`.
For example,
#define utstring_oom() do { longjmp(error_handling_location); } while (0)
...
#include "utstring.h"
// vim: set nowrap syntax=asciidoc:

View File

@@ -1 +0,0 @@
src

View File

@@ -1,28 +0,0 @@
{
"description": "C macros for hash tables and more",
"keywords": [
"array",
"data",
"hash",
"list",
"macro",
"string",
"structure",
"uthash"
],
"name": "uthash",
"repo": "troydhanson/uthash",
"src": [
"src/utarray.h",
"src/uthash.h",
"src/utlist.h",
"src/utringbuffer.h",
"src/utstack.h",
"src/utstring.h"
],
"version": "2.3.0"
}

View File

@@ -1,248 +0,0 @@
/*
Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* a dynamic array implementation using macros
*/
#ifndef UTARRAY_H
#define UTARRAY_H
#define UTARRAY_VERSION 2.3.0
#include <stddef.h> /* size_t */
#include <string.h> /* memset, etc */
#include <stdlib.h> /* exit */
#ifdef __GNUC__
#define UTARRAY_UNUSED __attribute__((__unused__))
#else
#define UTARRAY_UNUSED
#endif
#ifdef oom
#error "The name of macro 'oom' has been changed to 'utarray_oom'. Please update your code."
#define utarray_oom() oom()
#endif
#ifndef utarray_oom
#define utarray_oom() exit(-1)
#endif
typedef void (ctor_f)(void *dst, const void *src);
typedef void (dtor_f)(void *elt);
typedef void (init_f)(void *elt);
typedef struct {
size_t sz;
init_f *init;
ctor_f *copy;
dtor_f *dtor;
} UT_icd;
typedef struct {
unsigned i,n;/* i: index of next available slot, n: num slots */
UT_icd icd; /* initializer, copy and destructor functions */
char *d; /* n slots of size icd->sz*/
} UT_array;
#define utarray_init(a,_icd) do { \
memset(a,0,sizeof(UT_array)); \
(a)->icd = *(_icd); \
} while(0)
#define utarray_done(a) do { \
if ((a)->n) { \
if ((a)->icd.dtor) { \
unsigned _ut_i; \
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
(a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
} \
} \
free((a)->d); \
} \
(a)->n=0; \
} while(0)
#define utarray_new(a,_icd) do { \
(a) = (UT_array*)malloc(sizeof(UT_array)); \
if ((a) == NULL) { \
utarray_oom(); \
} \
utarray_init(a,_icd); \
} while(0)
#define utarray_free(a) do { \
utarray_done(a); \
free(a); \
} while(0)
#define utarray_reserve(a,by) do { \
if (((a)->i+(by)) > (a)->n) { \
char *utarray_tmp; \
while (((a)->i+(by)) > (a)->n) { (a)->n = ((a)->n ? (2*(a)->n) : 8); } \
utarray_tmp=(char*)realloc((a)->d, (a)->n*(a)->icd.sz); \
if (utarray_tmp == NULL) { \
utarray_oom(); \
} \
(a)->d=utarray_tmp; \
} \
} while(0)
#define utarray_push_back(a,p) do { \
utarray_reserve(a,1); \
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,(a)->i++), p); } \
else { memcpy(_utarray_eltptr(a,(a)->i++), p, (a)->icd.sz); }; \
} while(0)
#define utarray_pop_back(a) do { \
if ((a)->icd.dtor) { (a)->icd.dtor( _utarray_eltptr(a,--((a)->i))); } \
else { (a)->i--; } \
} while(0)
#define utarray_extend_back(a) do { \
utarray_reserve(a,1); \
if ((a)->icd.init) { (a)->icd.init(_utarray_eltptr(a,(a)->i)); } \
else { memset(_utarray_eltptr(a,(a)->i),0,(a)->icd.sz); } \
(a)->i++; \
} while(0)
#define utarray_len(a) ((a)->i)
#define utarray_eltptr(a,j) (((j) < (a)->i) ? _utarray_eltptr(a,j) : NULL)
#define _utarray_eltptr(a,j) ((void*)((a)->d + ((a)->icd.sz * (j))))
#define utarray_insert(a,p,j) do { \
if ((j) > (a)->i) utarray_resize(a,j); \
utarray_reserve(a,1); \
if ((j) < (a)->i) { \
memmove( _utarray_eltptr(a,(j)+1), _utarray_eltptr(a,j), \
((a)->i - (j))*((a)->icd.sz)); \
} \
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,j), p); } \
else { memcpy(_utarray_eltptr(a,j), p, (a)->icd.sz); }; \
(a)->i++; \
} while(0)
#define utarray_inserta(a,w,j) do { \
if (utarray_len(w) == 0) break; \
if ((j) > (a)->i) utarray_resize(a,j); \
utarray_reserve(a,utarray_len(w)); \
if ((j) < (a)->i) { \
memmove(_utarray_eltptr(a,(j)+utarray_len(w)), \
_utarray_eltptr(a,j), \
((a)->i - (j))*((a)->icd.sz)); \
} \
if ((a)->icd.copy) { \
unsigned _ut_i; \
for(_ut_i=0;_ut_i<(w)->i;_ut_i++) { \
(a)->icd.copy(_utarray_eltptr(a, (j) + _ut_i), _utarray_eltptr(w, _ut_i)); \
} \
} else { \
memcpy(_utarray_eltptr(a,j), _utarray_eltptr(w,0), \
utarray_len(w)*((a)->icd.sz)); \
} \
(a)->i += utarray_len(w); \
} while(0)
#define utarray_resize(dst,num) do { \
unsigned _ut_i; \
if ((dst)->i > (unsigned)(num)) { \
if ((dst)->icd.dtor) { \
for (_ut_i = (num); _ut_i < (dst)->i; ++_ut_i) { \
(dst)->icd.dtor(_utarray_eltptr(dst, _ut_i)); \
} \
} \
} else if ((dst)->i < (unsigned)(num)) { \
utarray_reserve(dst, (num) - (dst)->i); \
if ((dst)->icd.init) { \
for (_ut_i = (dst)->i; _ut_i < (unsigned)(num); ++_ut_i) { \
(dst)->icd.init(_utarray_eltptr(dst, _ut_i)); \
} \
} else { \
memset(_utarray_eltptr(dst, (dst)->i), 0, (dst)->icd.sz*((num) - (dst)->i)); \
} \
} \
(dst)->i = (num); \
} while(0)
#define utarray_concat(dst,src) do { \
utarray_inserta(dst, src, utarray_len(dst)); \
} while(0)
#define utarray_erase(a,pos,len) do { \
if ((a)->icd.dtor) { \
unsigned _ut_i; \
for (_ut_i = 0; _ut_i < (len); _ut_i++) { \
(a)->icd.dtor(utarray_eltptr(a, (pos) + _ut_i)); \
} \
} \
if ((a)->i > ((pos) + (len))) { \
memmove(_utarray_eltptr(a, pos), _utarray_eltptr(a, (pos) + (len)), \
((a)->i - ((pos) + (len))) * (a)->icd.sz); \
} \
(a)->i -= (len); \
} while(0)
#define utarray_renew(a,u) do { \
if (a) utarray_clear(a); \
else utarray_new(a, u); \
} while(0)
#define utarray_clear(a) do { \
if ((a)->i > 0) { \
if ((a)->icd.dtor) { \
unsigned _ut_i; \
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
(a)->icd.dtor(_utarray_eltptr(a, _ut_i)); \
} \
} \
(a)->i = 0; \
} \
} while(0)
#define utarray_sort(a,cmp) do { \
qsort((a)->d, (a)->i, (a)->icd.sz, cmp); \
} while(0)
#define utarray_find(a,v,cmp) bsearch((v),(a)->d,(a)->i,(a)->icd.sz,cmp)
#define utarray_front(a) (((a)->i) ? (_utarray_eltptr(a,0)) : NULL)
#define utarray_next(a,e) (((e)==NULL) ? utarray_front(a) : (((a)->i != utarray_eltidx(a,e)+1) ? _utarray_eltptr(a,utarray_eltidx(a,e)+1) : NULL))
#define utarray_prev(a,e) (((e)==NULL) ? utarray_back(a) : ((utarray_eltidx(a,e) != 0) ? _utarray_eltptr(a,utarray_eltidx(a,e)-1) : NULL))
#define utarray_back(a) (((a)->i) ? (_utarray_eltptr(a,(a)->i-1)) : NULL)
#define utarray_eltidx(a,e) (((char*)(e) - (a)->d) / (a)->icd.sz)
/* last we pre-define a few icd for common utarrays of ints and strings */
static void utarray_str_cpy(void *dst, const void *src) {
char *const *srcc = (char *const *)src;
char **dstc = (char**)dst;
*dstc = (*srcc == NULL) ? NULL : strdup(*srcc);
}
static void utarray_str_dtor(void *elt) {
char **eltc = (char**)elt;
if (*eltc != NULL) free(*eltc);
}
static const UT_icd ut_str_icd UTARRAY_UNUSED = {sizeof(char*),NULL,utarray_str_cpy,utarray_str_dtor};
static const UT_icd ut_int_icd UTARRAY_UNUSED = {sizeof(int),NULL,NULL,NULL};
static const UT_icd ut_ptr_icd UTARRAY_UNUSED = {sizeof(void*),NULL,NULL,NULL};
#endif /* UTARRAY_H */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,108 +0,0 @@
/*
Copyright (c) 2015-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* a ring-buffer implementation using macros
*/
#ifndef UTRINGBUFFER_H
#define UTRINGBUFFER_H
#define UTRINGBUFFER_VERSION 2.3.0
#include <stdlib.h>
#include <string.h>
#include "utarray.h" // for "UT_icd"
typedef struct {
unsigned i; /* index of next available slot; wraps at n */
unsigned n; /* capacity */
unsigned char f; /* full */
UT_icd icd; /* initializer, copy and destructor functions */
char *d; /* n slots of size icd->sz */
} UT_ringbuffer;
#define utringbuffer_init(a, _n, _icd) do { \
memset(a, 0, sizeof(UT_ringbuffer)); \
(a)->icd = *(_icd); \
(a)->n = (_n); \
if ((a)->n) { (a)->d = (char*)malloc((a)->n * (_icd)->sz); } \
} while(0)
#define utringbuffer_clear(a) do { \
if ((a)->icd.dtor) { \
if ((a)->f) { \
unsigned _ut_i; \
for (_ut_i = 0; _ut_i < (a)->n; ++_ut_i) { \
(a)->icd.dtor(utringbuffer_eltptr(a, _ut_i)); \
} \
} else { \
unsigned _ut_i; \
for (_ut_i = 0; _ut_i < (a)->i; ++_ut_i) { \
(a)->icd.dtor(utringbuffer_eltptr(a, _ut_i)); \
} \
} \
} \
(a)->i = 0; \
(a)->f = 0; \
} while(0)
#define utringbuffer_done(a) do { \
utringbuffer_clear(a); \
free((a)->d); (a)->d = NULL; \
(a)->n = 0; \
} while(0)
#define utringbuffer_new(a,n,_icd) do { \
a = (UT_ringbuffer*)malloc(sizeof(UT_ringbuffer)); \
utringbuffer_init(a, n, _icd); \
} while(0)
#define utringbuffer_free(a) do { \
utringbuffer_done(a); \
free(a); \
} while(0)
#define utringbuffer_push_back(a,p) do { \
if ((a)->icd.dtor && (a)->f) { (a)->icd.dtor(_utringbuffer_internalptr(a,(a)->i)); } \
if ((a)->icd.copy) { (a)->icd.copy( _utringbuffer_internalptr(a,(a)->i), p); } \
else { memcpy(_utringbuffer_internalptr(a,(a)->i), p, (a)->icd.sz); }; \
if (++(a)->i == (a)->n) { (a)->i = 0; (a)->f = 1; } \
} while(0)
#define utringbuffer_len(a) ((a)->f ? (a)->n : (a)->i)
#define utringbuffer_empty(a) ((a)->i == 0 && !(a)->f)
#define utringbuffer_full(a) ((a)->f != 0)
#define _utringbuffer_real_idx(a,j) ((a)->f ? ((j) + (a)->i) % (a)->n : (j))
#define _utringbuffer_internalptr(a,j) ((void*)((a)->d + ((a)->icd.sz * (j))))
#define utringbuffer_eltptr(a,j) ((0 <= (j) && (j) < utringbuffer_len(a)) ? _utringbuffer_internalptr(a,_utringbuffer_real_idx(a,j)) : NULL)
#define _utringbuffer_fake_idx(a,j) ((a)->f ? ((j) + (a)->n - (a)->i) % (a)->n : (j))
#define _utringbuffer_internalidx(a,e) (((char*)(e) >= (a)->d) ? (((char*)(e) - (a)->d)/(a)->icd.sz) : -1)
#define utringbuffer_eltidx(a,e) _utringbuffer_fake_idx(a, _utringbuffer_internalidx(a,e))
#define utringbuffer_front(a) utringbuffer_eltptr(a,0)
#define utringbuffer_next(a,e) ((e)==NULL ? utringbuffer_front(a) : utringbuffer_eltptr(a, utringbuffer_eltidx(a,e)+1))
#define utringbuffer_prev(a,e) ((e)==NULL ? utringbuffer_back(a) : utringbuffer_eltptr(a, utringbuffer_eltidx(a,e)-1))
#define utringbuffer_back(a) (utringbuffer_empty(a) ? NULL : utringbuffer_eltptr(a, utringbuffer_len(a) - 1))
#endif /* UTRINGBUFFER_H */

View File

@@ -1,88 +0,0 @@
/*
Copyright (c) 2018-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef UTSTACK_H
#define UTSTACK_H
#define UTSTACK_VERSION 2.3.0
/*
* This file contains macros to manipulate a singly-linked list as a stack.
*
* To use utstack, your structure must have a "next" pointer.
*
* ----------------.EXAMPLE -------------------------
* struct item {
* int id;
* struct item *next;
* };
*
* struct item *stack = NULL;
*
* int main() {
* int count;
* struct item *tmp;
* struct item *item = malloc(sizeof *item);
* item->id = 42;
* STACK_COUNT(stack, tmp, count); assert(count == 0);
* STACK_PUSH(stack, item);
* STACK_COUNT(stack, tmp, count); assert(count == 1);
* STACK_POP(stack, item);
* free(item);
* STACK_COUNT(stack, tmp, count); assert(count == 0);
* }
* --------------------------------------------------
*/
#define STACK_TOP(head) (head)
#define STACK_EMPTY(head) (!(head))
#define STACK_PUSH(head,add) \
STACK_PUSH2(head,add,next)
#define STACK_PUSH2(head,add,next) \
do { \
(add)->next = (head); \
(head) = (add); \
} while (0)
#define STACK_POP(head,result) \
STACK_POP2(head,result,next)
#define STACK_POP2(head,result,next) \
do { \
(result) = (head); \
(head) = (head)->next; \
} while (0)
#define STACK_COUNT(head,el,counter) \
STACK_COUNT2(head,el,counter,next) \
#define STACK_COUNT2(head,el,counter,next) \
do { \
(counter) = 0; \
for ((el) = (head); el; (el) = (el)->next) { ++(counter); } \
} while (0)
#endif /* UTSTACK_H */

View File

@@ -1,407 +0,0 @@
/*
Copyright (c) 2008-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* a dynamic string implementation using macros
*/
#ifndef UTSTRING_H
#define UTSTRING_H
#define UTSTRING_VERSION 2.3.0
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#ifdef __GNUC__
#define UTSTRING_UNUSED __attribute__((__unused__))
#else
#define UTSTRING_UNUSED
#endif
#ifdef oom
#error "The name of macro 'oom' has been changed to 'utstring_oom'. Please update your code."
#define utstring_oom() oom()
#endif
#ifndef utstring_oom
#define utstring_oom() exit(-1)
#endif
typedef struct {
char *d; /* pointer to allocated buffer */
size_t n; /* allocated capacity */
size_t i; /* index of first unused byte */
} UT_string;
#define utstring_reserve(s,amt) \
do { \
if (((s)->n - (s)->i) < (size_t)(amt)) { \
char *utstring_tmp = (char*)realloc( \
(s)->d, (s)->n + (amt)); \
if (!utstring_tmp) { \
utstring_oom(); \
} \
(s)->d = utstring_tmp; \
(s)->n += (amt); \
} \
} while(0)
#define utstring_init(s) \
do { \
(s)->n = 0; (s)->i = 0; (s)->d = NULL; \
utstring_reserve(s,100); \
(s)->d[0] = '\0'; \
} while(0)
#define utstring_done(s) \
do { \
if ((s)->d != NULL) free((s)->d); \
(s)->n = 0; \
} while(0)
#define utstring_free(s) \
do { \
utstring_done(s); \
free(s); \
} while(0)
#define utstring_new(s) \
do { \
(s) = (UT_string*)malloc(sizeof(UT_string)); \
if (!(s)) { \
utstring_oom(); \
} \
utstring_init(s); \
} while(0)
#define utstring_renew(s) \
do { \
if (s) { \
utstring_clear(s); \
} else { \
utstring_new(s); \
} \
} while(0)
#define utstring_clear(s) \
do { \
(s)->i = 0; \
(s)->d[0] = '\0'; \
} while(0)
#define utstring_bincpy(s,b,l) \
do { \
utstring_reserve((s),(l)+1); \
if (l) memcpy(&(s)->d[(s)->i], b, l); \
(s)->i += (l); \
(s)->d[(s)->i]='\0'; \
} while(0)
#define utstring_concat(dst,src) \
do { \
utstring_reserve((dst),((src)->i)+1); \
if ((src)->i) memcpy(&(dst)->d[(dst)->i], (src)->d, (src)->i); \
(dst)->i += (src)->i; \
(dst)->d[(dst)->i]='\0'; \
} while(0)
#define utstring_len(s) ((s)->i)
#define utstring_body(s) ((s)->d)
UTSTRING_UNUSED static void utstring_printf_va(UT_string *s, const char *fmt, va_list ap) {
int n;
va_list cp;
for (;;) {
#ifdef _WIN32
cp = ap;
#else
va_copy(cp, ap);
#endif
n = vsnprintf (&s->d[s->i], s->n-s->i, fmt, cp);
va_end(cp);
if ((n > -1) && ((size_t) n < (s->n-s->i))) {
s->i += n;
return;
}
/* Else try again with more space. */
if (n > -1) utstring_reserve(s,n+1); /* exact */
else utstring_reserve(s,(s->n)*2); /* 2x */
}
}
#ifdef __GNUC__
/* support printf format checking (2=the format string, 3=start of varargs) */
static void utstring_printf(UT_string *s, const char *fmt, ...)
__attribute__ (( format( printf, 2, 3) ));
#endif
UTSTRING_UNUSED static void utstring_printf(UT_string *s, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
utstring_printf_va(s,fmt,ap);
va_end(ap);
}
/*******************************************************************************
* begin substring search functions *
******************************************************************************/
/* Build KMP table from left to right. */
UTSTRING_UNUSED static void _utstring_BuildTable(
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
i = 0;
j = i - 1;
P_KMP_Table[i] = j;
while (i < (long) P_NeedleLen)
{
while ( (j > -1) && (P_Needle[i] != P_Needle[j]) )
{
j = P_KMP_Table[j];
}
i++;
j++;
if (i < (long) P_NeedleLen)
{
if (P_Needle[i] == P_Needle[j])
{
P_KMP_Table[i] = P_KMP_Table[j];
}
else
{
P_KMP_Table[i] = j;
}
}
else
{
P_KMP_Table[i] = j;
}
}
return;
}
/* Build KMP table from right to left. */
UTSTRING_UNUSED static void _utstring_BuildTableR(
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
i = P_NeedleLen - 1;
j = i + 1;
P_KMP_Table[i + 1] = j;
while (i >= 0)
{
while ( (j < (long) P_NeedleLen) && (P_Needle[i] != P_Needle[j]) )
{
j = P_KMP_Table[j + 1];
}
i--;
j--;
if (i >= 0)
{
if (P_Needle[i] == P_Needle[j])
{
P_KMP_Table[i + 1] = P_KMP_Table[j + 1];
}
else
{
P_KMP_Table[i + 1] = j;
}
}
else
{
P_KMP_Table[i + 1] = j;
}
}
return;
}
/* Search data from left to right. ( Multiple search mode. ) */
UTSTRING_UNUSED static long _utstring_find(
const char *P_Haystack,
size_t P_HaystackLen,
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
long V_FindPosition = -1;
/* Search from left to right. */
i = j = 0;
while ( (j < (int)P_HaystackLen) && (((P_HaystackLen - j) + i) >= P_NeedleLen) )
{
while ( (i > -1) && (P_Needle[i] != P_Haystack[j]) )
{
i = P_KMP_Table[i];
}
i++;
j++;
if (i >= (int)P_NeedleLen)
{
/* Found. */
V_FindPosition = j - i;
break;
}
}
return V_FindPosition;
}
/* Search data from right to left. ( Multiple search mode. ) */
UTSTRING_UNUSED static long _utstring_findR(
const char *P_Haystack,
size_t P_HaystackLen,
const char *P_Needle,
size_t P_NeedleLen,
long *P_KMP_Table)
{
long i, j;
long V_FindPosition = -1;
/* Search from right to left. */
j = (P_HaystackLen - 1);
i = (P_NeedleLen - 1);
while ( (j >= 0) && (j >= i) )
{
while ( (i < (int)P_NeedleLen) && (P_Needle[i] != P_Haystack[j]) )
{
i = P_KMP_Table[i + 1];
}
i--;
j--;
if (i < 0)
{
/* Found. */
V_FindPosition = j + 1;
break;
}
}
return V_FindPosition;
}
/* Search data from left to right. ( One time search mode. ) */
UTSTRING_UNUSED static long utstring_find(
UT_string *s,
long P_StartPosition, /* Start from 0. -1 means last position. */
const char *P_Needle,
size_t P_NeedleLen)
{
long V_StartPosition;
long V_HaystackLen;
long *V_KMP_Table;
long V_FindPosition = -1;
if (P_StartPosition < 0)
{
V_StartPosition = s->i + P_StartPosition;
}
else
{
V_StartPosition = P_StartPosition;
}
V_HaystackLen = s->i - V_StartPosition;
if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
{
V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
if (V_KMP_Table != NULL)
{
_utstring_BuildTable(P_Needle, P_NeedleLen, V_KMP_Table);
V_FindPosition = _utstring_find(s->d + V_StartPosition,
V_HaystackLen,
P_Needle,
P_NeedleLen,
V_KMP_Table);
if (V_FindPosition >= 0)
{
V_FindPosition += V_StartPosition;
}
free(V_KMP_Table);
}
}
return V_FindPosition;
}
/* Search data from right to left. ( One time search mode. ) */
UTSTRING_UNUSED static long utstring_findR(
UT_string *s,
long P_StartPosition, /* Start from 0. -1 means last position. */
const char *P_Needle,
size_t P_NeedleLen)
{
long V_StartPosition;
long V_HaystackLen;
long *V_KMP_Table;
long V_FindPosition = -1;
if (P_StartPosition < 0)
{
V_StartPosition = s->i + P_StartPosition;
}
else
{
V_StartPosition = P_StartPosition;
}
V_HaystackLen = V_StartPosition + 1;
if ( (V_HaystackLen >= (long) P_NeedleLen) && (P_NeedleLen > 0) )
{
V_KMP_Table = (long *)malloc(sizeof(long) * (P_NeedleLen + 1));
if (V_KMP_Table != NULL)
{
_utstring_BuildTableR(P_Needle, P_NeedleLen, V_KMP_Table);
V_FindPosition = _utstring_findR(s->d,
V_HaystackLen,
P_Needle,
P_NeedleLen,
V_KMP_Table);
free(V_KMP_Table);
}
}
return V_FindPosition;
}
/*******************************************************************************
* end substring search functions *
******************************************************************************/
#endif /* UTSTRING_H */

View File

@@ -1,120 +0,0 @@
#CC=clang
HASHDIR = ../src
UTILS = emit_keys
PROGS = test1 test2 test3 test4 test5 test6 test7 test8 test9 \
test10 test11 test12 test13 test14 test15 test16 test17 \
test18 test19 test20 test21 test22 test23 test24 test25 \
test26 test27 test28 test29 test30 test31 test32 test33 \
test34 test35 test36 test37 test38 test39 test40 test41 \
test42 test43 test44 test45 test46 test47 test48 test49 \
test50 test51 test52 test53 test54 test55 test56 test57 \
test58 test59 test60 test61 test62 test63 test64 test65 \
test66 test67 test68 test69 test70 test71 test72 test73 \
test74 test75 test76 test77 test78 test79 test80 test81 \
test82 test83 test84 test85 test86 test87 test88 test89 \
test90 test91 test92 test93 test94 test95 test96
CFLAGS += -I$(HASHDIR)
#CFLAGS += -DHASH_BLOOM=16
#CFLAGS += -O2
CFLAGS += -g
#CFLAGS += -Wstrict-aliasing=2
CFLAGS += -Wall
#CFLAGS += -Wextra
#CFLAGS += -std=c89
CFLAGS += ${EXTRA_CFLAGS}
ifeq ($(HASH_DEBUG),1)
CFLAGS += -DHASH_DEBUG=1
endif
ifeq ($(HASH_PEDANTIC),1)
CFLAGS += -pedantic
endif
TEST_TARGET=run_tests
TESTS=./do_tests
# detect Cygwin
ifneq ($(strip $(shell $(CC) -v 2>&1 |grep "cygwin")),)
TESTS=./do_tests.cygwin
endif
# detect MinGW
ifneq ($(strip $(shell $(CC) -v 2>&1 |grep "mingw")),)
TEST_TARGET=run_tests_mingw
TESTS=./do_tests.mingw
endif
#detect Linux (platform specific utilities)
ifneq ($(strip $(shell $(CC) -v 2>&1 |grep "linux")),)
PLAT_UTILS = hashscan sleep_test
endif
#detect FreeBSD (platform specific utilities)
ifeq ($(strip $(shell uname -s)), FreeBSD)
ifeq ($(shell if [ `sysctl -n kern.osreldate` -ge 0801000 ]; then echo "ok"; fi), ok)
PLAT_UTILS = hashscan sleep_test
endif
endif
all: $(PROGS) $(UTILS) $(PLAT_UTILS) keystat $(TEST_TARGET)
tests_only: $(PROGS) $(TEST_TARGET)
GITIGN = .gitignore
MKGITIGN = [ -f "$(GITIGN)" ] || echo "$(GITIGN)" > $(GITIGN); grep -q '^\$@$$' $(GITIGN) || echo "$@" >> $(GITIGN)
debug:
$(MAKE) all HASH_DEBUG=1
pedantic:
$(MAKE) all HASH_PEDANTIC=1
cplusplus:
CC="$(CXX) -x c++" $(MAKE) all
thorough:
$(MAKE) clean && $(MAKE) all EXTRA_CFLAGS='-pedantic'
$(MAKE) clean && $(MAKE) all EXTRA_CFLAGS='-pedantic -DHASH_BLOOM=16'
$(MAKE) clean && $(MAKE) tests_only EXTRA_CFLAGS='-pedantic -DHASH_BLOOM=16 -DHASH_DEBUG -DNO_DECLTYPE'
$(MAKE) clean && CC="$(CXX) -x c++" $(MAKE) all EXTRA_CFLAGS='-pedantic'
$(MAKE) clean && CC="$(CXX) -x c++" $(MAKE) all EXTRA_CFLAGS='-pedantic -DHASH_BLOOM=16'
$(MAKE) clean && CC="$(CXX) -x c++" $(MAKE) tests_only EXTRA_CFLAGS='-pedantic -DHASH_BLOOM=16 -DHASH_DEBUG -DNO_DECLTYPE'
example: example.c $(HASHDIR)/uthash.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(@).c
$(PROGS) $(UTILS) : $(HASHDIR)/uthash.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(@).c
@$(MKGITIGN)
hashscan : $(HASHDIR)/uthash.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(@).c
@$(MKGITIGN)
sleep_test : $(HASHDIR)/uthash.h
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_BLOOM=16 $(LDFLAGS) -o $@ $(@).c
@$(MKGITIGN)
keystat : $(HASHDIR)/uthash.h
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_FUNCTION=HASH_BER $(LDFLAGS) -o keystat.BER keystat.c
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_FUNCTION=HASH_FNV $(LDFLAGS) -o keystat.FNV keystat.c
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_FUNCTION=HASH_JEN $(LDFLAGS) -o keystat.JEN keystat.c
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_FUNCTION=HASH_OAT $(LDFLAGS) -o keystat.OAT keystat.c
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_FUNCTION=HASH_SAX $(LDFLAGS) -o keystat.SAX keystat.c
$(CC) $(CPPFLAGS) $(CFLAGS) -DHASH_FUNCTION=HASH_SFH $(LDFLAGS) -o keystat.SFH keystat.c
run_tests: $(PROGS)
perl $(TESTS)
run_tests_mingw: $(PROGS)
/bin/bash do_tests.mingw
astyle:
astyle -n --style=kr --indent-switches --add-brackets *.c
.PHONY: clean astyle
clean:
rm -f $(UTILS) $(PLAT_UTILS) $(PROGS) test*.out keystat.??? example hashscan sleep_test *.exe $(GITIGN)
rm -rf *.dSYM

View File

@@ -1,132 +0,0 @@
Automated tests for uthash
==============================================================================
Run "make" in this directory to build the tests and run them.
test1: make 10-item hash, iterate and print each one
test2: make 10-item hash, lookup items with even keys, print
test3: make 10-item hash, delete items with even keys, print others
test4: 10 structs have dual hash handles, separate keys
test5: 10 structs have dual hash handles, lookup evens by alt key
test6: test alt malloc macros (and alt key-comparison macro)
test7: test alt malloc macros with 1000 structs so bucket expansion occurs
test8: test num_items counter in UT_hash_handle
test9: test "find" after bucket expansion
test10: dual-hash handle test, bucket expansion on one and not the other
test11: read dat file of names into hash, sort them and print
test12: create hash with string keys, add 10 items, lookup each item
test13: make 10-item hash, delete items with even keys, reverse print others
test14: read dat file of names into hash, read file again and lookup each one
test15: build string-keyed hash of 3 items, lookup one item (c.f. test40.c)
test16: hash on aggregate key, iterate, lookup, using generalized macros
test17: sort, add more items, sort again
test18: test pathological HASH_DEL(a,a) scenario (single head,deletee variable)
test19: sort two hash tables with shared elements using HASH_SRT
test20: test a 5-byte "binary" key
test21: test a structure key (userguide)
test22: test multi-field key using flexible array member (userguide utf32)
test23: test whether delete in iteration works
test24: make 10-item hash and confirm item count (HASH_COUNT)
test25: CDL / DL / LL tests
test26: test the linked list sort macros in utlist.h
test27: LL_APPEND, SORT
test28: CDL / DL / LL tests
test29: DL_APPEND, SORT
test30: CDL_PREPEND, SORT
test31: CDL_PREPEND, SORT
test32: DL_PREPEND
test33: LL_PREPEND
test34: CDL_PREPEND
test35: CDL_PREPEND
test36: HASH_SELECT
test37: HASH_CLEAR
test38: find-or-add test on integer keys in short loop
test39: HASH_ADD_KEYPTR then HASH_FIND using array element as key pointer
test40: HASH_ADD_KEYPTR on string keys; pointer equivalent to test15.c
test41: test LL_FOREACH_SAFE,DL_FOREACH_SAFE,CDL_FOREACH_SAFE
test42: test LL_SEARCH, LL_SEARCH_SCALAR, and DL and CDL counterparts
test43: test utarray with intpair objects
test44: test utarray with int objects
test45: test utarray with int objects
test46: test utarray with char* objects
test47: test utstring
test48: test utarray of int
test49: test utarray of str
test50: test utarray of long
test51: test utarray of intpair
test52: test utarray of intchar
test53: test utstring
test54: test utstring
test55: test utstring
test56: test uthash, utlist and utstring together for #define conflicts etc
test57: test uthash HASH_ADD_PTR and HASH_FIND_PTR
test58: test HASH_ITER macro
test59: sample of multi-level hash
test60: sample of multi-level hash that also does HASH_DEL and free
test61: test utarray_find
test62: test macros used in safe unaligned reads on non-Intel type platforms
test63: LL_CONCAT test
test64: DL_CONCAT test
test65: LRU cache example courtesy of jehiah.cz with modifications
test66: test example where output variable to HASH_FIND needs extra parens
test67: test utarray_prev
test68: test DL_REPLACE_ELEM (Zoltán Lajos Kis)
test69: test DL_PREPEND_ELEM (Zoltán Lajos Kis)
test70: test LL_REPLACE_ELEM (Zoltán Lajos Kis)
test71: test LL_PREPEND_ELEM (Zoltán Lajos Kis)
test72: test CDL_REPLACE_ELEM (Zoltán Lajos Kis)
test73: test CDL_PREPEND_ELEM (Zoltán Lajos Kis)
test74: test utstring with utstring_find (Joe Wei)
test75: test utstring with utstring_findR (Joe Wei)
test76: test utstring with _utstring_find (Joe Wei)
test77: test utstring with _utstring_findR (Joe Wei)
test78: test utlist "2" family with flexible Prev/Next naming eg. DL_DELETE2
test79: test HASH_REPLACE
test80: test utarray_insert past end of array
test81: test utarray_insert past end of array
test82: test utarray_inserta past end of array
test83: test HASH_REPLACE_STR with char[] key
test84: test HASH_REPLACE_STR with char* key
test85: test HASH_OVERHEAD on null and non null hash
test86: test *_APPEND_ELEM / *_PREPEND_ELEM (Thilo Schulz)
test87: test HASH_ADD_INORDER() macro (Thilo Schulz)
test88: test alt key-comparison and strlen macros
test89: test code from the tinydtls project
test90: regression-test HASH_ADD_KEYPTR_INORDER (IronBug)
test91: test LL_INSERT_INORDER etc.
test92: HASH_NONFATAL_OOM
test93: alt_fatal
test94: utlist with fields named other than 'next' and 'prev'
test95: utstack
test96: HASH_FUNCTION + HASH_KEYCMP
Other Make targets
================================================================================
pedantic: makes the tests with extra CFLAGS for pedantic compiling
cplusplus: compiles all the C tests using the C++ compiler to test compatibility
debug: makes the tests with debugging symbols and no optimization
example: builds the 'example' program from the user guide
================================================================================
Testing a specific hash function
--------------------------------
Set EXTRA_CFLAGS with this Makefile to use a specific hash function:
EXTRA_CFLAGS=-DHASH_FUNCTION=HASH_BER make
Other files
================================================================================
keystats: key statistics analyzer. See the uthash User Guide.
emit_keys: reads a data file of unique strings, emits as keys w/HASH_EMIT_KEYS=1
all_funcs: a script which executes the test suite with every hash function
win32tests:builds and runs the test suite under Microsoft Visual Studio
LINUX/FREEBSD
-------------
hashscan: tool to examine a running process and get info on its hash tables
test_sleep:used as a subject for inspection by hashscan
Manual performance testing
================================================================================
# test performance characteristics on keys that are English dictionary words
emit_keys /usr/share/dict/words > words.keys
./keystats words.keys

View File

@@ -1,13 +0,0 @@
#!/bin/bash
function proceed {
read -p "proceed ? [n] " response
if [ "$response" != "y" ]; then exit -1; fi
}
make clean tests_only EXTRA_CFLAGS='-DHASH_FUNCTION=HASH_BER'; proceed
make clean tests_only EXTRA_CFLAGS='-DHASH_FUNCTION=HASH_FNV'; proceed
make clean tests_only EXTRA_CFLAGS='-DHASH_FUNCTION=HASH_JEN'; proceed
make clean tests_only EXTRA_CFLAGS='-DHASH_FUNCTION=HASH_OAT'; proceed
make clean tests_only EXTRA_CFLAGS='-DHASH_FUNCTION=HASH_SAX'; proceed
make clean tests_only EXTRA_CFLAGS='-DHASH_FUNCTION=HASH_SFH'; proceed

View File

@@ -1,82 +0,0 @@
#include <stdlib.h> /* malloc */
#include <sys/time.h> /* gettimeofday */
#include <errno.h> /* perror */
#include <stdio.h> /* printf */
#include "uthash.h"
#define BUFLEN 20
#if 0
#undef uthash_expand_fyi
#define uthash_expand_fyi(tbl) printf("expanding to %d buckets\n", tbl->num_buckets)
#endif
typedef struct name_rec {
char boy_name[BUFLEN];
UT_hash_handle hh;
} name_rec;
int main(int argc,char *argv[])
{
name_rec *name, *names=NULL;
char linebuf[BUFLEN];
FILE *file;
int i=0,j,nloops=3,loopnum=0,miss;
struct timeval tv1,tv2;
long elapsed_usec;
if (argc > 1) {
nloops = atoi(argv[1]);
}
if ( (file = fopen( "test14.dat", "r" )) == NULL ) {
perror("can't open: ");
exit(-1);
}
while (fgets(linebuf,BUFLEN,file) != NULL) {
i++;
if ( (name = (name_rec*)malloc(sizeof(name_rec))) == NULL) {
exit(-1);
}
strcpy(name->boy_name, linebuf);
HASH_ADD_STR(names,boy_name,name);
}
again:
if (fseek(file,0,SEEK_SET) == -1) {
fprintf(stderr,"fseek failed: %s\n", strerror(errno));
}
j=0;
if (gettimeofday(&tv1,NULL) == -1) {
perror("gettimeofday: ");
}
while (fgets(linebuf,BUFLEN,file) != NULL) {
/* if we do 10 loops, the first has a 0% miss rate,
* the second has a 10% miss rate, etc */
miss = ((rand()*1.0/RAND_MAX) < (loopnum*1.0/nloops)) ? 1 : 0;
/* generate a miss if we want one */
if (miss) {
linebuf[0]++;
if (linebuf[1] != '\0') {
linebuf[1]++;
}
}
HASH_FIND_STR(names,linebuf,name);
if (name) {
j++;
}
}
if (gettimeofday(&tv2,NULL) == -1) {
perror("gettimeofday: ");
}
elapsed_usec = ((tv2.tv_sec - tv1.tv_sec) * 1000000) + (tv2.tv_usec - tv1.tv_usec);
printf("lookup on %d of %d (%.2f%%) names succeeded (%.2f usec)\n", j, i,
j*100.0/i, (double)(elapsed_usec));
if (++loopnum < nloops) {
goto again;
}
fclose(file);
return 0;
}

View File

@@ -1,17 +0,0 @@
#!/bin/bash
BITS="16"
cc -I../src -O3 -Wall -m64 bloom_perf.c -o bloom_perf.none
for bits in $BITS
do
cc -I../src -DHASH_BLOOM=$bits -O3 -Wall -m64 bloom_perf.c -o bloom_perf.$bits
done
for bits in none $BITS
do
echo
echo "using $bits-bit filter:"
./bloom_perf.$bits 10
done

View File

@@ -1,21 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my @tests;
for (glob "test*[0-9]") {
push @tests, $_ if -e "$_.ans";
}
my $num_failed=0;
for my $test (@tests) {
`./$test > $test.out`;
`diff $test.out $test.ans`;
print "$test failed\n" if $?;
$num_failed++ if $?;
}
print scalar @tests . " tests conducted, $num_failed failed.\n";
exit $num_failed;

View File

@@ -1,22 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
my @tests;
for (glob "test*[0-9].exe") {
push @tests, "$_" if -e substr($_, 0, - 4).".ans";
}
my $num_failed=0;
for my $test (@tests) {
`./$test > $test.out`;
my $ansfile = substr($test, 0, - 4).".ans";
`diff $test.out $ansfile`;
print "$test failed\n" if $?;
$num_failed++ if $?;
}
print scalar @tests . " tests conducted, $num_failed failed.\n";
exit $num_failed;

View File

@@ -1,20 +0,0 @@
#!/bin/bash
echo "MinGW test script starting"
for f in test*.exe
do
t=`echo $f | sed s/.exe//`
"./$f" > "$t.out"
diff -qb "$t.out" "$t.ans"
if [ $? -eq 1 ]
then
echo "$f failed"
else
true # can't have empty else
#echo "$f passed"
fi
done
echo
echo "All tests complete"

View File

@@ -1,16 +0,0 @@
:: this compiles and runs the test suite under Visual Studio 2008
::@echo off
call "C:\Program Files\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat" > vc.out
::call "C:\Program Files\Microsoft Visual Studio 10.0\VC\bin\vcvars32.bat" > vc.out
set "COMPILE=cl.exe /I ..\src /EHsc /nologo"
echo compiling...
%COMPILE% tdiff.cpp > compile.out
::for %%f in (test*.c) do %COMPILE% /Tp %%f >> compile.out
for %%f in (test*.c) do %COMPILE% /Tc %%f >> compile.out
echo running tests...
for %%f in (test*.exe) do %%f > %%~nf.out
echo scanning for failures...
for %%f in (test*.out) do tdiff %%f %%~nf.ans
echo tests completed
::for %%f in (test*.out test*.obj test*.exe vc.out compile.out tdiff.obj tdiff.exe) do del %%f
pause

View File

@@ -1,48 +0,0 @@
#include <stdlib.h> /* malloc */
#include <errno.h> /* perror */
#include <stdio.h> /* printf */
#include <unistd.h> /* write */
/* this define must precede uthash.h */
#define HASH_EMIT_KEYS 1
#include "uthash.h"
#define BUFLEN 30
typedef struct name_rec {
char boy_name[BUFLEN];
UT_hash_handle hh;
} name_rec;
int main(int argc,char *argv[])
{
name_rec *name, *names=NULL;
char linebuf[BUFLEN];
FILE *file;
int i=0;
if (argc != 2) {
fprintf(stderr,"usage: %s file\n", argv[0]);
exit(-1);
}
if ( (file = fopen( argv[1], "r" )) == NULL ) {
perror("can't open: ");
exit(-1);
}
while (fgets(linebuf,BUFLEN,file) != NULL) {
name = (name_rec*)malloc(sizeof(name_rec));
if (name == NULL) {
exit(-1);
}
strcpy(name->boy_name, linebuf);
HASH_ADD_STR(names,boy_name,name);
i++;
}
fprintf(stderr,"%d keys emitted.\n", i);
fclose(file);
return 0;
}

View File

@@ -1,147 +0,0 @@
#include <stdio.h> /* printf */
#include <stdlib.h> /* atoi, malloc */
#include <string.h> /* strcpy */
#include "uthash.h"
struct my_struct {
int id; /* key */
char name[21];
UT_hash_handle hh; /* makes this structure hashable */
};
struct my_struct *users = NULL;
void add_user(int user_id, const char *name)
{
struct my_struct *s;
HASH_FIND_INT(users, &user_id, s); /* id already in the hash? */
if (s == NULL) {
s = (struct my_struct*)malloc(sizeof *s);
s->id = user_id;
HASH_ADD_INT(users, id, s); /* id is the key field */
}
strcpy(s->name, name);
}
struct my_struct *find_user(int user_id)
{
struct my_struct *s;
HASH_FIND_INT(users, &user_id, s); /* s: output pointer */
return s;
}
void delete_user(struct my_struct *user)
{
HASH_DEL(users, user); /* user: pointer to deletee */
free(user);
}
void delete_all()
{
struct my_struct *current_user;
struct my_struct *tmp;
HASH_ITER(hh, users, current_user, tmp) {
HASH_DEL(users, current_user); /* delete it (users advances to next) */
free(current_user); /* free it */
}
}
void print_users()
{
struct my_struct *s;
for (s = users; s != NULL; s = (struct my_struct*)(s->hh.next)) {
printf("user id %d: name %s\n", s->id, s->name);
}
}
int by_name(const struct my_struct *a, const struct my_struct *b)
{
return strcmp(a->name, b->name);
}
int by_id(const struct my_struct *a, const struct my_struct *b)
{
return (a->id - b->id);
}
const char *getl(const char *prompt)
{
static char buf[21];
char *p;
printf("%s? ", prompt); fflush(stdout);
p = fgets(buf, sizeof(buf), stdin);
if (p == NULL || (p = strchr(buf, '\n')) == NULL) {
puts("Invalid input!");
exit(EXIT_FAILURE);
}
*p = '\0';
return buf;
}
int main()
{
int id = 1;
int running = 1;
struct my_struct *s;
int temp;
while (running) {
printf(" 1. add user\n");
printf(" 2. add or rename user by id\n");
printf(" 3. find user\n");
printf(" 4. delete user\n");
printf(" 5. delete all users\n");
printf(" 6. sort items by name\n");
printf(" 7. sort items by id\n");
printf(" 8. print users\n");
printf(" 9. count users\n");
printf("10. quit\n");
switch (atoi(getl("Command"))) {
case 1:
add_user(id++, getl("Name (20 char max)"));
break;
case 2:
temp = atoi(getl("ID"));
add_user(temp, getl("Name (20 char max)"));
break;
case 3:
s = find_user(atoi(getl("ID to find")));
printf("user: %s\n", s ? s->name : "unknown");
break;
case 4:
s = find_user(atoi(getl("ID to delete")));
if (s) {
delete_user(s);
} else {
printf("id unknown\n");
}
break;
case 5:
delete_all();
break;
case 6:
HASH_SORT(users, by_name);
break;
case 7:
HASH_SORT(users, by_id);
break;
case 8:
print_users();
break;
case 9:
temp = HASH_COUNT(users);
printf("there are %d users\n", temp);
break;
case 10:
running = 0;
break;
}
}
delete_all(); /* free any structures */
return 0;
}

View File

@@ -1,678 +0,0 @@
/*
Copyright (c) 2005-2021, Troy D. Hanson http://troydhanson.github.io/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/types.h> /* on OSX, must come before ptrace.h */
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#ifdef __FreeBSD__
#include <sys/param.h> /* MAXPATHLEN */
#include <vm/vm.h> /* VM_PROT_* flags */
#endif
#if defined(PT_ATTACH) && !defined(PTRACE_ATTACH)
#define PTRACE_ATTACH PT_ATTACH
#define PTRACE_DETACH PT_DETACH
#endif
/* need this defined so offsetof can give us bloom offsets in UT_hash_table */
#define HASH_BLOOM 16
#include "uthash.h"
#ifdef __FreeBSD__
typedef struct {
void *start;
void *end;
} vma_t;
#else
typedef struct {
off_t start;
off_t end;
char perms[4]; /* rwxp */
char device[5]; /* fd:01 or 00:00 */
} vma_t;
#endif
const uint32_t sig = HASH_SIGNATURE;
int verbose=0;
int getkeys=0;
#define vv(...) do {if (verbose>0) printf(__VA_ARGS__);} while(0)
#define vvv(...) do {if (verbose>1) printf(__VA_ARGS__);} while(0)
/* these id's are arbitrary, only meaningful within this file */
#define JEN 1
#define BER 2
#define SFH 3
#define SAX 4
#define FNV 5
#define OAT 6
#define NUM_HASH_FUNCS 7 /* includes id 0, the non-function */
const char *hash_fcns[] = {"???","JEN","BER","SFH","SAX","FNV","OAT"};
/* given a peer key/len/hashv, reverse engineer its hash function */
static int infer_hash_function(char *key, size_t keylen, uint32_t hashv)
{
uint32_t ohashv;
/* BER SAX FNV OAT JEN SFH */
HASH_JEN(key,keylen,ohashv);
if (ohashv == hashv) {
return JEN;
}
HASH_BER(key,keylen,ohashv);
if (ohashv == hashv) {
return BER;
}
HASH_SFH(key,keylen,ohashv);
if (ohashv == hashv) {
return SFH;
}
HASH_SAX(key,keylen,ohashv);
if (ohashv == hashv) {
return SAX;
}
HASH_FNV(key,keylen,ohashv);
if (ohashv == hashv) {
return FNV;
}
HASH_OAT(key,keylen,ohashv);
if (ohashv == hashv) {
return OAT;
}
return 0;
}
/* read peer's memory from addr for len bytes, store into our dst */
#ifdef __FreeBSD__
static int read_mem(void *dst, pid_t pid, void *start, size_t len)
{
struct ptrace_io_desc io_desc;
int ret;
io_desc.piod_op = PIOD_READ_D;
io_desc.piod_offs = start;
io_desc.piod_addr = dst;
io_desc.piod_len = len;
ret = ptrace(PT_IO, pid, (void *) &io_desc, 0);
if (ret) {
vv("read_mem: ptrace failed: %s\n", strerror(errno));
return -1;
} else if (io_desc.piod_len != len) {
vv("read_mem: short read!\n");
return -1;
}
return 0;
}
#else
static int read_mem(void *dst, int fd, off_t start, size_t len)
{
int rc;
size_t bytes_read=0;
if (lseek(fd, start, SEEK_SET) == (off_t)-1) {
fprintf(stderr, "lseek failed: %s\n", strerror(errno));
return -1;
}
while ( len && ((rc=read(fd, (char*)dst+bytes_read, len)) > 0)) {
len -= rc;
bytes_read += rc;
}
if (rc==-1) {
vv("read_mem failed (%s)\n",strerror(errno));
}
if ((len != 0 && rc >= 0)) {
vv("INTERNAL ERROR\n");
}
return (rc == -1) ? -1 : 0;
}
#endif
/* later compensate for possible presence of bloom filter */
static char *tbl_from_sig_addr(char *sig)
{
return (sig - offsetof(UT_hash_table,signature));
}
#define HS_BIT_TEST(v,i) (v[i/8] & (1U << (i%8)))
static void found(int fd, char* peer_sig, pid_t pid)
{
UT_hash_table *tbl=NULL;
UT_hash_bucket *bkts=NULL;
UT_hash_handle hh;
size_t i, bloom_len, bloom_bitlen, bloom_on_bits=0,bloom_off_bits=0;
char *peer_tbl, *peer_bloom_sig, *peer_bloom_nbits, *peer_bloombv_ptr,
*peer_bloombv, *peer_bkts, *peer_hh, *key=NULL;
const char *peer_key;
const char *hash_fcn = NULL;
unsigned char *bloombv=NULL;
static int fileno=0;
char keyfile[50];
unsigned char bloom_nbits=0;
int keyfd=-1, mode=S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
hash_fcn_hits[NUM_HASH_FUNCS], hash_fcn_winner;
unsigned max_chain=0;
uint32_t bloomsig;
int has_bloom_filter_fields = 0;
for(i=0; i < NUM_HASH_FUNCS; i++) {
hash_fcn_hits[i]=0;
}
if (getkeys) {
snprintf(keyfile, sizeof(keyfile), "/tmp/%u-%u.key", (unsigned)pid,fileno++);
if ( (keyfd = open(keyfile, O_WRONLY|O_CREAT|O_TRUNC, mode)) == -1) {
fprintf(stderr, "can't open %s: %s\n", keyfile, strerror(errno));
exit(-1);
}
}
vv("found signature at peer %p\n", (void*)peer_sig);
peer_tbl = tbl_from_sig_addr(peer_sig);
vvv("reading table at peer %p\n", (void*)peer_tbl);
if ( (tbl = (UT_hash_table*)malloc(sizeof(UT_hash_table))) == NULL) {
fprintf(stderr, "out of memory\n");
exit(-1);
}
#ifdef __FreeBSD__
if (read_mem(tbl, pid, (void *)peer_tbl, sizeof(UT_hash_table)) != 0) {
#else
if (read_mem(tbl, fd, (off_t)peer_tbl, sizeof(UT_hash_table)) != 0) {
#endif
fprintf(stderr, "failed to read peer memory\n");
goto done;
}
/* got the table. how about the buckets */
peer_bkts = (char*)tbl->buckets;
vvv("reading %u buckets at peer %p\n", tbl->num_buckets, (void*)peer_bkts);
bkts = (UT_hash_bucket*)malloc(sizeof(UT_hash_bucket)*tbl->num_buckets);
if (bkts == NULL) {
fprintf(stderr, "out of memory\n");
goto done;
}
#ifdef __FreeBSD__
if (read_mem(bkts, pid, (void *)peer_bkts, sizeof(UT_hash_bucket)*tbl->num_buckets) != 0) {
#else
if (read_mem(bkts, fd, (off_t)peer_bkts, sizeof(UT_hash_bucket)*tbl->num_buckets) != 0) {
#endif
fprintf(stderr, "failed to read peer memory\n");
goto done;
}
vvv("scanning %u peer buckets\n", tbl->num_buckets);
for(i=0; i < tbl->num_buckets; i++) {
vvv("bucket %u has %u items\n", (unsigned)i, (unsigned)(bkts[i].count));
if (bkts[i].count > max_chain) {
max_chain = bkts[i].count;
}
if (bkts[i].expand_mult) {
vvv(" bucket %u has expand_mult %u\n", (unsigned)i, (unsigned)(bkts[i].expand_mult));
}
vvv("scanning bucket %u chain:\n", (unsigned)i);
peer_hh = (char*)bkts[i].hh_head;
while(peer_hh) {
#ifdef __FreeBSD__
if (read_mem(&hh, pid, (void *)peer_hh, sizeof(hh)) != 0) {
#else
if (read_mem(&hh, fd, (off_t)peer_hh, sizeof(hh)) != 0) {
#endif
fprintf(stderr, "failed to read peer memory\n");
goto done;
}
if ((char*)hh.tbl != peer_tbl) {
goto done;
}
peer_hh = (char*)hh.hh_next;
peer_key = (const char*)(hh.key);
/* malloc space to read the key, and read it */
if ( (key = (char*)malloc(sizeof(hh.keylen))) == NULL) {
fprintf(stderr, "out of memory\n");
exit(-1);
}
#ifdef __FreeBSD__
if (read_mem(key, pid, (void*)peer_key, hh.keylen) != 0) {
#else
if (read_mem(key, fd, (off_t)peer_key, hh.keylen) != 0) {
#endif
fprintf(stderr, "failed to read peer memory\n");
goto done;
}
hash_fcn_hits[infer_hash_function(key,hh.keylen,hh.hashv)]++;
/* write the key if requested */
if (getkeys) {
write(keyfd, &hh.keylen, sizeof(unsigned));
write(keyfd, key, hh.keylen);
}
free(key);
key=NULL;
}
}
/* does it have a bloom filter? */
peer_bloom_sig = peer_tbl + offsetof(UT_hash_table, bloom_sig);
peer_bloombv_ptr = peer_tbl + offsetof(UT_hash_table, bloom_bv);
peer_bloom_nbits = peer_tbl + offsetof(UT_hash_table, bloom_nbits);
vvv("looking for bloom signature at peer %p\n", (void*)peer_bloom_sig);
#ifdef __FreeBSD__
if ((read_mem(&bloomsig, pid, (void *)peer_bloom_sig, sizeof(uint32_t)) == 0) &&
(bloomsig == HASH_BLOOM_SIGNATURE)) {
#else
if ((read_mem(&bloomsig, fd, (off_t)peer_bloom_sig, sizeof(uint32_t)) == 0) &&
(bloomsig == HASH_BLOOM_SIGNATURE)) {
#endif
vvv("bloom signature (%x) found\n",bloomsig);
/* bloom found. get at bv, nbits */
#ifdef __FreeBSD__
if (read_mem(&bloom_nbits, pid, (void *)peer_bloom_nbits, sizeof(char)) == 0) {
#else
if (read_mem(&bloom_nbits, fd, (off_t)peer_bloom_nbits, sizeof(char)) == 0) {
#endif
/* scan bloom filter, calculate saturation */
bloom_bitlen = (1ULL << bloom_nbits);
bloom_len = (bloom_bitlen / 8) + ((bloom_bitlen % 8) ? 1 : 0);
vvv("bloom bitlen is %u, bloom_bytelen is %u\n", (unsigned)bloom_bitlen, (unsigned)bloom_len);
if ( (bloombv = (unsigned char*)malloc(bloom_len)) == NULL) {
fprintf(stderr, "out of memory\n");
exit(-1);
}
/* read the address of the bitvector in the peer, then read the bv itself */
#ifdef __FreeBSD__
if ((read_mem(&peer_bloombv, pid, (void *)peer_bloombv_ptr, sizeof(void*)) == 0) &&
(read_mem(bloombv, pid, (void *)peer_bloombv, bloom_len) == 0)) {
#else
if ((read_mem(&peer_bloombv, fd, (off_t)peer_bloombv_ptr, sizeof(void*)) == 0) &&
(read_mem(bloombv, fd, (off_t)peer_bloombv, bloom_len) == 0)) {
#endif
/* calculate saturation */
vvv("read peer bloom bitvector from %p (%u bytes)\n", (void*)peer_bloombv, (unsigned)bloom_len);
for(i=0; i < bloom_bitlen; i++) {
if (HS_BIT_TEST(bloombv,(unsigned)i)) {
/* vvv("bit %u set\n",(unsigned)i); */
bloom_on_bits++;
} else {
bloom_off_bits++;
}
}
has_bloom_filter_fields = 1;
vvv("there were %u on_bits among %u total bits\n", (unsigned)bloom_on_bits, (unsigned)bloom_bitlen);
}
}
}
/* choose apparent hash function */
hash_fcn_winner=0;
for(i=0; i<NUM_HASH_FUNCS; i++) {
if (hash_fcn_hits[i] > hash_fcn_hits[hash_fcn_winner]) {
hash_fcn_winner=i;
}
}
hash_fcn = hash_fcns[hash_fcn_winner];
/*
Address ideal items buckets mc fl bloom sat fcn keys saved to
------------------ ----- -------- -------- -- -- ----- ----- --- -------------
0x10aa4090 98% 10000000 32000000 10 ok BER /tmp/9110-0.key
0x10abcdef 100% 10000000 32000000 9 NX 27 12% BER /tmp/9110-1.key
*/
printf("Address ideal items buckets mc fl bloom sat fcn keys saved to\n");
printf("------------------ ----- -------- -------- -- -- ----- ----- --- -------------\n");
if (has_bloom_filter_fields) {
printf("%-18p %4.0f%% %8u %8u %2u %2s %5u %4.0f%c %3s %s\n",
(void*)peer_tbl,
(tbl->num_items - tbl->nonideal_items) * 100.0 / tbl->num_items,
tbl->num_items,
tbl->num_buckets,
max_chain,
tbl->noexpand ? "NX" : "ok",
bloom_nbits,
bloom_on_bits * 100.0 / bloom_bitlen, '%',
hash_fcn,
(getkeys ? keyfile : ""));
} else {
printf("%-18p %4.0f%% %8u %8u %2u %2s %5s %4s%c %3s %s\n",
(void*)peer_tbl,
(tbl->num_items - tbl->nonideal_items) * 100.0 / tbl->num_items,
tbl->num_items,
tbl->num_buckets,
max_chain,
tbl->noexpand ? "NX" : "ok",
"",
"", ' ',
hash_fcn,
(getkeys ? keyfile : ""));
}
#if 0
printf("read peer tbl:\n");
printf("num_buckets: %u\n", tbl->num_buckets);
printf("num_items: %u\n", tbl->num_items);
printf("nonideal_items: %u (%.2f%%)\n", tbl->nonideal_items,
tbl->nonideal_items*100.0/tbl->num_items);
printf("expand: %s\n", tbl->noexpand ? "inhibited": "normal");
if (getkeys) {
printf("keys written to %s\n", keyfile);
}
#endif
done:
if (bkts) {
free(bkts);
}
if (tbl) {
free(tbl);
}
if (key) {
free(key);
}
if (keyfd != -1) {
close(keyfd);
}
if (bloombv) {
free(bloombv);
}
}
#ifdef __FreeBSD__
static void sigscan(pid_t pid, void *start, void *end, uint32_t sig)
{
struct ptrace_io_desc io_desc;
int page_size = getpagesize();
char *buf;
char *pos;
/* make sure page_size is a multiple of the signature size, code below assumes this */
assert(page_size % sizeof(sig) == 0);
buf = malloc(page_size);
if (buf == NULL) {
fprintf(stderr, "malloc failed in sigscan()\n");
return;
}
io_desc.piod_op = PIOD_READ_D;
io_desc.piod_offs = start;
io_desc.piod_addr = buf;
io_desc.piod_len = page_size;
/* read in one page after another and search sig */
while(!ptrace(PT_IO, pid, (void *) &io_desc, 0)) {
if (io_desc.piod_len != page_size) {
fprintf(stderr, "PT_IO returned less than page size in sigscan()\n");
return;
}
/* iterate over the the page using the signature size and look for the sig */
for (pos = buf; pos < (buf + page_size); pos += sizeof(sig)) {
if (*(uint32_t *) pos == sig) {
found(pid, (char *) io_desc.piod_offs + (pos - buf), pid);
}
}
/*
* 'end' is inclusive (the address of the last valid byte), so if the current offset
* plus a page is beyond 'end', we're already done. since all vm map entries consist
* of entire pages and 'end' is inclusive, current offset plus one page should point
* exactly one byte beyond 'end'. this is assert()ed below to be on the safe side.
*/
if (io_desc.piod_offs + page_size > end) {
assert(io_desc.piod_offs + page_size == (end + 1));
break;
}
/* advance to the next page */
io_desc.piod_offs += page_size;
}
}
#else
static void sigscan(int fd, off_t start, off_t end, uint32_t sig, pid_t pid)
{
int rlen;
uint32_t u;
off_t at=0;
if (lseek(fd, start, SEEK_SET) == (off_t)-1) {
fprintf(stderr, "lseek failed: %s\n", strerror(errno));
return;
}
while ( (rlen = read(fd,&u,sizeof(u))) == sizeof(u)) {
if (!memcmp(&u,&sig,sizeof(u))) {
found(fd, (char*)(start+at),pid);
}
at += sizeof(u);
if ((off_t)(at + sizeof(u)) > end-start) {
break;
}
}
if (rlen == -1) {
//fprintf(stderr,"read failed: %s\n", strerror(errno));
//exit(-1);
}
}
#endif
#ifdef __FreeBSD__
static int scan(pid_t pid)
{
vma_t *vmas=NULL, vma;
unsigned i, num_vmas = 0;
int ret;
struct ptrace_vm_entry vm_entry;
char path[MAXPATHLEN];
vv("attaching to peer\n");
if (ptrace(PT_ATTACH,pid,NULL,0) == -1) {
fprintf(stderr,"failed to attach to %u: %s\n", (unsigned)pid, strerror(errno));
exit(EXIT_FAILURE);
}
vv("waiting for peer to suspend temporarily\n");
if (waitpid(pid,NULL,0) != pid) {
fprintf(stderr,"failed to wait for pid %u: %s\n",(unsigned)pid, strerror(errno));
goto die;
}
/* read memory map using ptrace */
vv("listing peer virtual memory areas\n");
vm_entry.pve_entry = 0;
vm_entry.pve_path = path; /* not used but required to make vm_entry.pve_pathlen work */
while(1) {
/* set pve_pathlen every turn, it gets overwritten by ptrace */
vm_entry.pve_pathlen = MAXPATHLEN;
errno = 0;
ret = ptrace(PT_VM_ENTRY, pid, (void *) &vm_entry, 0);
if (ret) {
if (errno == ENOENT) {
/* we've reached the last entry */
break;
}
fprintf(stderr, "fetching vm map entry failed: %s (%i)\n", strerror(errno), errno);
goto die;
}
vvv("vmmap entry: start: %p, end: %p", (void *) vm_entry.pve_start, (void *) vm_entry.pve_end);
/* skip unreadable or vnode-backed entries */
if (!(vm_entry.pve_prot & VM_PROT_READ) || vm_entry.pve_pathlen > 0) {
vvv(" -> skipped (not readable or vnode-backed)\n");
vm_entry.pve_path[0] = 0;
continue;
}
/* useful entry, add to list */
vvv(" -> will be scanned\n");
vma.start = (void *)vm_entry.pve_start;
vma.end = (void *)vm_entry.pve_end;
vmas = (vma_t *) realloc(vmas, (num_vmas + 1) * sizeof(vma_t));
if (vmas == NULL) {
exit(-1);
}
vmas[num_vmas++] = vma;
}
vv("peer has %u virtual memory areas\n", num_vmas);
/* look for the hash signature */
vv("scanning peer memory for hash table signatures\n");
for(i=0; i<num_vmas; i++) {
vma = vmas[i];
sigscan(pid, vma.start, vma.end, sig);
}
die:
vv("detaching and resuming peer\n");
if (ptrace(PT_DETACH, pid, NULL, 0) == -1) {
fprintf(stderr,"failed to detach from %u: %s\n", (unsigned)pid, strerror(errno));
}
return 0;
}
# else
static int scan(pid_t pid)
{
FILE *mapf;
char mapfile[30], memfile[30], line[100];
vma_t *vmas=NULL, vma;
unsigned i, num_vmas = 0;
int memfd;
void *pstart, *pend, *unused;
/* attach to the target process and wait for it to suspend */
vv("attaching to peer\n");
if (ptrace(PTRACE_ATTACH, pid, NULL, 0) == -1) {
fprintf(stderr,"failed to attach to %u: %s\n", (unsigned)pid, strerror(errno));
exit(-1);
}
vv("waiting for peer to suspend temporarily\n");
if (waitpid(pid,NULL,0) != pid) {
fprintf(stderr,"failed to wait for pid %u: %s\n",(unsigned)pid, strerror(errno));
goto die;
}
/* get ready to open its memory map. this gives us its valid memory areas */
snprintf(mapfile,sizeof(mapfile),"/proc/%u/maps",(unsigned)pid);
snprintf(memfile,sizeof(memfile),"/proc/%u/mem", (unsigned)pid);
vv("opening peer memory map [%s]\n", mapfile);
if ( (mapf = fopen(mapfile,"r")) == NULL) {
fprintf(stderr,"failed to open %s: %s\n", mapfile, strerror(errno));
goto die;
}
vv("listing peer virtual memory areas\n");
while(fgets(line,sizeof(line),mapf)) {
if (sscanf(line, "%p-%p %4c %p %5c", &pstart, &pend, vma.perms,
&unused, vma.device) == 5) {
vma.start = (off_t)pstart;
vma.end = (off_t)pend;
if (vma.perms[0] != 'r') {
continue; /* only readable vma's */
}
if (memcmp(vma.device,"fd",2)==0) {
continue; /* skip mapped files */
}
vmas = (vma_t*)realloc(vmas, (num_vmas+1) * sizeof(vma_t));
if (vmas == NULL) {
exit(-1);
}
vmas[num_vmas++] = vma;
}
}
vv("peer has %u virtual memory areas\n",num_vmas);
fclose(mapf);
/* ok, open up its memory and start looking around in there */
vv("opening peer memory\n");
if ( (memfd=open(memfile,O_RDONLY)) == -1) {
fprintf(stderr,"failed to open %s: %s\n", memfile, strerror(errno));
goto die;
}
/* look for the hash signature */
vv("scanning peer memory for hash table signatures\n");
for(i=0; i<num_vmas; i++) {
vma = vmas[i];
pstart = (void*)vma.start;
pend = (void*)vma.end;
/*fprintf(stderr,"scanning %p-%p %.4s %.5s\n", pstart, pend,
vma.perms, vma.device);*/
sigscan(memfd, vma.start, vma.end, sig, pid);
}
/* done. close memory and detach. this resumes the target process */
close(memfd);
die:
vv("detaching and resuming peer\n");
if (ptrace(PTRACE_DETACH, pid, NULL, 0) == -1) {
fprintf(stderr,"failed to detach from %u: %s\n", (unsigned)pid, strerror(errno));
}
return 0;
}
#endif
static int usage(const char *prog)
{
fprintf(stderr,"usage: %s [-v] [-k] <pid>\n", prog);
return -1;
}
int main(int argc, char *argv[])
{
int opt;
while ( (opt = getopt(argc, argv, "kv")) != -1) {
switch (opt) {
case 'v':
verbose++;
break;
case 'k':
getkeys++;
break;
default:
return usage(argv[0]);
}
}
if (optind < argc) {
pid_t pid = atoi(argv[optind++]);
return scan(pid);
} else {
return usage(argv[0]);
}
}

View File

@@ -1,255 +0,0 @@
#include <sys/types.h> /* for 'open' */
#include <sys/stat.h> /* for 'open' */
#include <fcntl.h> /* for 'open' */
#include <stdlib.h> /* for 'malloc' */
#include <stdio.h> /* for 'printf' */
#include <unistd.h> /* for 'read' */
#include <errno.h> /* for 'sterror' */
#include <sys/time.h> /* for 'gettimeofday' */
#include "uthash.h"
#undef uthash_noexpand_fyi
#define uthash_noexpand_fyi(t) die()
#define UNALIGNED_KEYS 0
static void die()
{
fprintf(stderr,"expansion inhibited\n");
exit(-1);
}
/* Windows doesn't have gettimeofday. While Cygwin and some
* versions of MinGW supply one, it is very coarse. This substitute
* gives much more accurate elapsed times under Windows. */
#if (( defined __CYGWIN__ ) || ( defined __MINGW32__ ))
#include <windows.h>
static void win_gettimeofday(struct timeval* p, void* tz /* IGNORED */)
{
LARGE_INTEGER q;
static long long freq;
static long long cyg_timer;
QueryPerformanceFrequency(&q);
freq = q.QuadPart;
QueryPerformanceCounter(&q);
cyg_timer = q.QuadPart;
p->tv_sec = (long)(cyg_timer / freq);
p->tv_usec = (long)(((cyg_timer % freq) * 1000000) / freq);
}
#define gettimeofday win_gettimeofday
#define MODE (O_RDONLY|O_BINARY)
#else
#define MODE (O_RDONLY)
#endif
#ifndef timersub
#define timersub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) { \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
#endif
typedef struct stat_key {
char *key;
unsigned len;
UT_hash_handle hh, hh2;
} stat_key;
#define CHAIN_0 0
#define CHAIN_5 1
#define CHAIN_10 2
#define CHAIN_20 3
#define CHAIN_100 4
#define CHAIN_MAX 5
static void hash_chain_len_histogram(const UT_hash_table *tbl)
{
unsigned i, bkt_hist[CHAIN_MAX+1];
double pct = 100.0/(double)tbl->num_buckets;
memset(bkt_hist,0,sizeof(bkt_hist));
for(i=0; i < tbl->num_buckets; i++) {
unsigned count = tbl->buckets[i].count;
if (count == 0U) {
bkt_hist[CHAIN_0]++;
} else if (count < 5U) {
bkt_hist[CHAIN_5]++;
} else if (count < 10U) {
bkt_hist[CHAIN_10]++;
} else if (count < 20U) {
bkt_hist[CHAIN_20]++;
} else if (count < 100U) {
bkt_hist[CHAIN_100]++;
} else {
bkt_hist[CHAIN_MAX]++;
}
}
fprintf(stderr, "Buckets with 0 items: %.1f%%\n", (double)bkt_hist[CHAIN_0 ]*pct);
fprintf(stderr, "Buckets with < 5 items: %.1f%%\n", (double)bkt_hist[CHAIN_5 ]*pct);
fprintf(stderr, "Buckets with < 10 items: %.1f%%\n", (double)bkt_hist[CHAIN_10]*pct);
fprintf(stderr, "Buckets with < 20 items: %.1f%%\n", (double)bkt_hist[CHAIN_20]*pct);
fprintf(stderr, "Buckets with < 100 items: %.1f%%\n", (double)bkt_hist[CHAIN_100]*pct);
fprintf(stderr, "Buckets with > 100 items: %.1f%%\n", (double)bkt_hist[CHAIN_MAX]*pct);
}
int main(int argc, char *argv[])
{
int dups=0, rc, fd, done=0, err=0, want, i, padding=0, v=1, percent=100;
unsigned keylen, max_keylen=0, verbose=0;
const char *filename = "/dev/stdin";
char *dst;
stat_key *keyt, *keytmp, *keys=NULL, *keys2=NULL;
struct timeval start_tm, end_tm, elapsed_tm, elapsed_tm2, elapsed_tm3;
if ((argc >= 3) && (strcmp(argv[1],"-p") == 0)) {
percent = atoi(argv[2]);
v = 3;
}
if ((v < argc) && (strcmp(argv[v],"-v") == 0)) {
verbose=1;
v++;
}
if (v < argc) {
filename=argv[v];
}
fd=open(filename,MODE);
if ( fd == -1 ) {
fprintf(stderr,"open failed %s: %s\n", filename, strerror(errno));
return -1;
}
for(i=0; done==0; i++) {
want = sizeof(int);
dst = (char*)&keylen;
readmore1:
rc = read(fd,dst,want);
if (rc != want) {
if (rc == 0) {
done=1;
} else if (rc == -1) {
fprintf(stderr,"read failed: %s\n", strerror(errno));
err=1;
} else if (rc > 0) {
want -= rc;
dst += rc;
goto readmore1;
}
}
if (done || err) {
break;
}
if (keylen > max_keylen) {
max_keylen=keylen;
}
keyt = (stat_key*)malloc(sizeof(stat_key));
if (keyt == NULL) {
fprintf(stderr,"out of memory\n");
exit(-1);
}
/* read key */
#ifdef UNALIGNED_KEYS
padding = i%8;
#endif
keyt->key = (char*)malloc(padding+keylen);
if (keyt->key == NULL) {
fprintf(stderr,"out of memory\n");
exit(-1);
}
keyt->key += padding; /* forcibly alter the alignment of key */
keyt->len = keylen;
want = keylen;
dst = keyt->key;
readmore2:
rc = read(fd,dst,want);
if (rc != want) {
if (rc == -1) {
fprintf(stderr,"read failed: %s\n", strerror(errno));
err=1;
} else if (rc == 0) {
fprintf(stderr,"incomplete file\n");
err=1;
} else if (rc >= 0) {
want -= rc;
dst += rc;
goto readmore2;
}
}
if (err != 0) {
break;
}
/* if percent was set to something less than 100%, skip some keys*/
if (((rand()*1.0) / RAND_MAX) > ((percent*1.0)/100)) {
free(keyt->key-padding);
free(keyt);
continue;
}
/* eliminate dups */
HASH_FIND(hh,keys,keyt->key,keylen,keytmp);
if (keytmp != NULL) {
dups++;
free(keyt->key - padding);
free(keyt);
} else {
HASH_ADD_KEYPTR(hh,keys,keyt->key,keylen,keyt);
}
}
if (verbose != 0) {
unsigned key_count = HASH_COUNT(keys);
fprintf(stderr,"max key length: %u\n", max_keylen);
fprintf(stderr,"number unique keys: %u\n", key_count);
fprintf(stderr,"keystats memory: %u\n",
(unsigned)((sizeof(stat_key)+max_keylen)*key_count));
hash_chain_len_histogram(keys->hh.tbl);
}
/* add all keys to a new hash, so we can measure add time w/o malloc */
gettimeofday(&start_tm,NULL);
for(keyt = keys; keyt != NULL; keyt=(stat_key*)keyt->hh.next) {
HASH_ADD_KEYPTR(hh2,keys2,keyt->key,keyt->len,keyt);
}
gettimeofday(&end_tm,NULL);
timersub(&end_tm, &start_tm, &elapsed_tm);
/* now look up all keys in the new hash, again measuring elapsed time */
gettimeofday(&start_tm,NULL);
for(keyt = keys; keyt != NULL; keyt=(stat_key*)keyt->hh.next) {
HASH_FIND(hh2,keys2,keyt->key,keyt->len,keytmp);
if (keytmp == NULL) {
fprintf(stderr,"internal error, key not found\n");
}
}
gettimeofday(&end_tm,NULL);
timersub(&end_tm, &start_tm, &elapsed_tm2);
/* now delete all items in the new hash, measuring elapsed time */
gettimeofday(&start_tm,NULL);
while (keys2 != NULL) {
keytmp = keys2;
HASH_DELETE(hh2,keys2,keytmp);
}
gettimeofday(&end_tm,NULL);
timersub(&end_tm, &start_tm, &elapsed_tm3);
if (err == 0) {
printf("%.3f,%u,%u,%d,%s,%ld,%ld,%ld\n",
1-(1.0*keys->hh.tbl->nonideal_items/keys->hh.tbl->num_items),
keys->hh.tbl->num_items,
keys->hh.tbl->num_buckets,
dups,
(keys->hh.tbl->noexpand != 0U) ? "nx" : "ok",
(elapsed_tm.tv_sec * 1000000) + elapsed_tm.tv_usec,
(elapsed_tm2.tv_sec * 1000000) + elapsed_tm2.tv_usec,
(elapsed_tm3.tv_sec * 1000000) + elapsed_tm3.tv_usec );
}
return 0;
}

View File

@@ -1,42 +0,0 @@
#!/usr/bin/perl
use strict;
use FindBin;
sub usage {
print "usage: keystats [-v] keyfile\n";
print "usage: keystats [-p <pct> [-v]] keyfile\n";
exit -1;
}
usage if ((@ARGV == 0) or ($ARGV[0] eq '-h'));
my @exes = glob "'$FindBin::Bin/keystat.???'";
my %stats;
for my $exe (@exes) {
$exe =~ s/\ /\\ /g;
$stats{$exe} = `$exe @ARGV`;
delete $stats{$exe} if ($? != 0); # omit hash functions that fail to produce stats (nx)
}
print( "fcn ideal% #items #buckets dup% fl add_usec find_usec del-all usec\n");
printf("--- ------ ---------- ---------- ----- -- ---------- ---------- ------------\n");
for my $exe (sort statsort keys %stats) {
my ($ideal,$items,$bkts,$dups,$ok,$add,$find,$del) = split /,/, $stats{$exe};
# convert 0-1 values to percentages
$dups = $items ? (100.0 * $dups / $items) : 0.0;
$ideal = 100.0 * $ideal;
printf("%3s %5.1f%% %10d %10d %4.0f%% %2s %10d %10d %12d\n", substr($exe,-3,3),
$ideal,$items,$bkts,$dups,$ok,$add,$find,$del);
}
# sort on hash_q (desc) then by find_usec (asc)
sub statsort {
my @a_stats = split /,/, $stats{$a};
my @b_stats = split /,/, $stats{$b};
return ($b_stats[0] <=> $a_stats[0]) || ($a_stats[-1] <=> $b_stats[-1]);
}

View File

@@ -1,21 +0,0 @@
CC=gcc
CFLAGS+=-W -Werror -Wall -Wextra -std=c99 \
-D_FORTIFY_SOURCE=2 -fstack-protector -g \
-Wformat=2 -pedantic -pedantic-errors \
-D_GNU_SOURCE=1 -D_BSD_SOURCE=1 \
-I../../src
LDFLAGS+=-pthread
cache: main.o cache.o
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) main.o cache.o -o cache
main.o: main.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c main.c -o main.o
cache.o: cache.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c cache.c -o cache.o
clean:
rm -f cache *.o

View File

@@ -1,221 +0,0 @@
/*
* =====================================================================================
*
* Filename: cache.c
*
* Description: A simple cache
*
* Version: 1.0
* Created: 04/11/2013 02:31:02 PM
* Revision: none
* Compiler: gcc
*
* Author: Oliver Lorenz (ol), olli@olorenz.org
* Company: https://olorenz.org
* License: This is licensed under the same terms as uthash itself
*
* =====================================================================================
*/
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include "cache.h"
#include "uthash.h"
/**
* A cache entry
*/
struct foo_cache_entry {
char *key; /**<The key */
void *data; /**<Payload */
UT_hash_handle hh; /**<Hash Handle for uthash */
};
#define KEY_MAX_LENGTH 32
/**
* A cache object
*/
struct foo_cache {
size_t max_entries; /**<Amount of entries this cache object can hold */
pthread_rwlock_t cache_lock; /**<A lock for concurrent access */
struct foo_cache_entry *entries; /**<Head pointer for uthash */
void (*free_cb) (void *element);/**<Callback function to free cache entries */
};
/** Creates a new cache object
@param dst
Where the newly allocated cache object will be stored in
@param capacity
The maximum number of elements this cache object can hold
@return EINVAL if dst is NULL, ENOMEM if malloc fails, 0 otherwise
*/
int foo_cache_create(struct foo_cache **dst, const size_t capacity,
void (*free_cb) (void *element))
{
struct foo_cache *new = NULL;
int rv;
if (!dst)
return EINVAL;
if ((new = malloc(sizeof(*new))) == NULL)
return ENOMEM;
if ((rv = pthread_rwlock_init(&(new->cache_lock), NULL)) != 0)
goto err_out;
new->max_entries = capacity;
new->entries = NULL;
new->free_cb = free_cb;
*dst = new;
return 0;
err_out:
if (new)
free(new);
return rv;
}
/** Frees an allocated cache object
@param cache
The cache object to free
@param keep_data
Whether to free contained data or just delete references to it
@return EINVAL if cache is NULL, 0 otherwise
*/
int foo_cache_delete(struct foo_cache *cache, int keep_data)
{
struct foo_cache_entry *entry, *tmp;
int rv;
if (!cache)
return EINVAL;
rv = pthread_rwlock_wrlock(&(cache->cache_lock));
if (rv)
return rv;
if (keep_data) {
HASH_CLEAR(hh, cache->entries);
} else {
HASH_ITER(hh, cache->entries, entry, tmp) {
HASH_DEL(cache->entries, entry);
if (cache->free_cb)
cache->free_cb(entry->data);
free(entry);
}
}
(void)pthread_rwlock_unlock(&(cache->cache_lock));
(void)pthread_rwlock_destroy(&(cache->cache_lock));
free(cache);
cache = NULL;
return 0;
}
/** Checks if a given key is in the cache
@param cache
The cache object
@param key
The key to look-up
@param result
Where to store the result if key is found.
A warning: Even though result is just a pointer,
you have to call this function with a **ptr,
otherwise this will blow up in your face.
@return EINVAL if cache is NULL, 0 otherwise
*/
int foo_cache_lookup(struct foo_cache *cache, char *key, void *result)
{
int rv;
struct foo_cache_entry *tmp = NULL;
char **dirty_hack = result;
if (!cache || !key || !result)
return EINVAL;
rv = pthread_rwlock_wrlock(&(cache->cache_lock));
if (rv)
return rv;
HASH_FIND_STR(cache->entries, key, tmp);
if (tmp) {
size_t key_len = strnlen(tmp->key, KEY_MAX_LENGTH);
HASH_DELETE(hh, cache->entries, tmp);
HASH_ADD_KEYPTR(hh, cache->entries, tmp->key, key_len, tmp);
*dirty_hack = tmp->data;
} else {
*dirty_hack = result = NULL;
}
rv = pthread_rwlock_unlock(&(cache->cache_lock));
return rv;
}
/** Inserts a given <key, value> pair into the cache
@param cache
The cache object
@param key
The key that identifies <value>
@param data
Data associated with <key>
@return EINVAL if cache is NULL, ENOMEM if malloc fails, 0 otherwise
*/
int foo_cache_insert(struct foo_cache *cache, char *key, void *data)
{
struct foo_cache_entry *entry = NULL;
struct foo_cache_entry *tmp_entry = NULL;
size_t key_len = 0;
int rv;
if (!cache || !data)
return EINVAL;
if ((entry = malloc(sizeof(*entry))) == NULL)
return ENOMEM;
if ((rv = pthread_rwlock_wrlock(&(cache->cache_lock))) != 0)
goto err_out;
entry->key = key;
entry->data = data;
key_len = strnlen(entry->key, KEY_MAX_LENGTH);
HASH_ADD_KEYPTR(hh, cache->entries, entry->key, key_len, entry);
if (HASH_COUNT(cache->entries) >= cache->max_entries) {
HASH_ITER(hh, cache->entries, entry, tmp_entry) {
HASH_DELETE(hh, cache->entries, entry);
if (cache->free_cb)
cache->free_cb(entry->data);
else
free(entry->data);
/* free(key->key) if data has been copied */
free(entry);
break;
}
}
rv = pthread_rwlock_unlock(&(cache->cache_lock));
return rv;
err_out:
if (entry)
free(entry);
(void)pthread_rwlock_unlock(&(cache->cache_lock));
return rv;
}

View File

@@ -1,31 +0,0 @@
/*
* =====================================================================================
*
* Filename: cache.h
*
* Description: A simple cache
*
* Version: 1.0
* Created: 04/11/2013 02:30:46 PM
* Revision: none
* Compiler: gcc
*
* Author: Oliver Lorenz (ol), olli@olorenz.org
* Company: https://olorenz.org
* License: This is licensed under the same terms as uthash itself
*
* =====================================================================================
*/
#ifndef _CACHE_
#define _CACHE_
struct foo_cache;
extern int foo_cache_create(struct foo_cache **dst, const size_t capacity,
void (*free_cb) (void *element));
extern int foo_cache_delete(struct foo_cache *cache, int keep_data);
extern int foo_cache_lookup(struct foo_cache *cache, char *key, void *result);
extern int foo_cache_insert(struct foo_cache *cache, char *key, void *data);
#endif

View File

@@ -1,191 +0,0 @@
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "cache.h"
#define MAX_RANDOM_ENTRIES 32
struct key_record {
char *key;
char *value;
};
int generate_random_entry(struct key_record **entry);
int generate_random_string(char **dst, const size_t len);
void free_random_entry(void *entry);
void *producer(void *arg)
{
struct foo_cache *cache = arg;
int i;
for (i = 0; i < MAX_RANDOM_ENTRIES; i++) {
struct key_record *entry = NULL;
if (generate_random_entry(&entry)) {
fprintf(stderr, "generate_random_entry() failed\n");
continue;
}
#if defined(DEBUG)
printf("Random Entry:\n");
printf(" key: %s\n", entry->key);
printf(" Key: %s\n", entry->value);
#else
printf("inserted %s (%d)\n", entry->key,
(int)strlen(entry->key));
#endif
if (foo_cache_insert(cache, entry->key, entry)) {
fprintf(stderr, "foo_cache_insert() failed\n");
continue;
}
}
pthread_exit(NULL);
}
void *consumer(void *arg)
{
struct foo_cache *cache = arg;
struct key_record *result = NULL;
char *buffer = malloc(64);
char key[33];
int stop = 0;
if (!buffer)
goto out;
/* give producer time to populate the cache */
sleep(2);
printf("\n\n");
do {
memset(key, 0, 64);
result = NULL;
printf("Enter key for lookup: ");
fgets(buffer, sizeof(key), stdin);
sscanf(buffer, "%s\n", key);
/* read '\n' from stdin */
getchar();
if (strncmp(key, "exit", 4) == 0) {
stop = 1;
continue;
}
printf("Got key %s (%d)\n", key, (int)strlen(key));
if (foo_cache_lookup(cache, key, &result)) {
fprintf(stderr, "Could not retrieve key %s\n", key);
continue;
}
if (!result) {
printf("MISS\n");
continue;
}
printf("HIT\n");
printf("key: %s\n", result->key);
printf("key : %s\n", result->value);
} while (!stop);
out:
if (buffer)
free(buffer);
pthread_exit(NULL);
}
int main()
{
int rv;
struct foo_cache *cache = NULL;
pthread_t workers[2];
rv = foo_cache_create(&cache, MAX_RANDOM_ENTRIES / 2,
free_random_entry);
if (rv) {
fprintf(stderr, "Could not create cache\n");
exit(1);
}
(void)pthread_create(&workers[0], NULL, producer, (void *)cache);
(void)pthread_create(&workers[1], NULL, consumer, (void *)cache);
pthread_join(workers[0], NULL);
pthread_join(workers[1], NULL);
(void)foo_cache_delete(cache, 0);
return 0;
}
int generate_random_entry(struct key_record **entry)
{
struct key_record *new = NULL;
char *key = NULL;
char *value = NULL;
int rv;
if (!entry)
return EINVAL;
rv = generate_random_string(&key, 33);
if (rv)
return rv;
rv = generate_random_string(&value, 129);
if (rv)
return rv;
if ((new = malloc(sizeof(*new))) == NULL) {
free(key);
free(value);
return ENOMEM;
}
new->key = key;
new->value = value;
*entry = new;
return 0;
}
int generate_random_string(char **dst, const size_t len)
{
static const char alphanum[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
size_t i;
char *s;
if (!dst || len == 0)
return EINVAL;
if ((s = malloc(len)) == NULL)
return ENOMEM;
for (i = 0; i < len - 1; i++) {
s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
}
s[len - 1] = '\0';
*dst = s;
return 0;
}
void free_random_entry(void *entry)
{
#if defined(DEBUG)
fprintf(stderr, "In %s: entry @ %p\n", __func__, entry);
#endif
struct key_record *record = entry;
if (!record)
return;
if (record->key)
free(record->key);
if (record->value)
free(record->value);
free(record);
record = NULL;
}

View File

@@ -1,28 +0,0 @@
#!/usr/bin/perl
# This program generates a simkey10.dat (100, 1000, etc) each
# containing 100 random keys of length 10 (100, 1000, etc).
# These files can then be fed into keystats to observe that
# the time to add or find the keys is directly proportional to
# keylength n [in other words, O(n)].
#
# The conclusion is that really long keys (e.g. 100k) are not
# efficient. TDH 23Jan07
use strict;
use warnings;
#for my $len (10,100,1000,10000,100000,1000000) {
for my $len (100) {
open OUTFILE, ">simkeys$len.dat" or die "can't open: $!\n";
# we'll do 100 keys of $len
print "keylen $len\n";
for my $i (0..99) {
my $key = pack "I", $len;
$key .= pack "C", (int(rand(256))) for (1..$len);
print OUTFILE $key;
}
close OUTFILE;
}

View File

@@ -1,32 +0,0 @@
#include "uthash.h"
#include <stdlib.h> /* malloc */
#include <stdio.h> /* printf */
#include <unistd.h> /* getpid */
typedef struct example_user_t {
int id;
int cookie;
UT_hash_handle hh;
} example_user_t;
int main()
{
int i;
example_user_t *user, *users=NULL;
/* create elements */
for(i=0; i<10000; i++) {
if ( (user = (example_user_t*)malloc(sizeof(example_user_t))) == NULL) {
exit(-1);
}
user->id = i;
user->cookie = i*i;
HASH_ADD_INT(users,id,user);
}
printf("pid: %u\n", (unsigned)getpid());
/* printf("sig: %p\n", &users->hh.tbl->signature); */
/* printf("bbv: %p\n", &users->hh.tbl->bloom_bv); */
sleep(60*10);
return 0;
}

View File

@@ -1,34 +0,0 @@
// Windows does not have unix diff so this is a simple replacement
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[] ) {
int rc=-1;
if (argc != 3) {
cout << "usage: " << argv[0] << " file1 file2\n";
return -1;
}
char *file1 = argv[1];
char *file2 = argv[2];
ifstream is1(file1, ios::in);
ifstream is2(file2, ios::in);
if (is1.fail()) {cerr << "failed to open " << file1 << "\n"; goto done;}
if (is2.fail()) {cerr << "failed to open " << file2 << "\n"; goto done;}
char d1[256], d2[256];
do {
is1.read(d1,sizeof(d1));
is2.read(d2,sizeof(d2));
if ((is1.gcount() != is2.gcount()) || memcmp(d1,d2,is1.gcount())) {
cout << file1 << " and " << file2 << " differ\n";
goto done;
}
} while (!is1.eof() && !is2.eof());
rc=0;
done:
is1.close();
is2.close();
return rc;
}

View File

@@ -1,10 +0,0 @@
user 0, cookie 0
user 1, cookie 1
user 2, cookie 4
user 3, cookie 9
user 4, cookie 16
user 5, cookie 25
user 6, cookie 36
user 7, cookie 49
user 8, cookie 64
user 9, cookie 81

View File

@@ -1,31 +0,0 @@
#include "uthash.h"
#include <stdlib.h> /* malloc */
#include <stdio.h> /* printf */
typedef struct example_user_t {
int id;
int cookie;
UT_hash_handle hh;
} example_user_t;
int main()
{
int i;
example_user_t *user, *users=NULL;
/* create elements */
for(i=0; i<10; i++) {
user = (example_user_t*)malloc(sizeof(example_user_t));
if (user == NULL) {
exit(-1);
}
user->id = i;
user->cookie = i*i;
HASH_ADD_INT(users,id,user);
}
for(user=users; user != NULL; user=(example_user_t*)(user->hh.next)) {
printf("user %d, cookie %d\n", user->id, user->cookie);
}
return 0;
}

View File

@@ -1,4 +0,0 @@
9 found in hh
9 found in alth
10 not found in hh
10 found in alth

View File

@@ -1,54 +0,0 @@
#include "uthash.h"
#include <stdlib.h> /* malloc */
#include <stdio.h> /* printf */
typedef struct example_user_t {
int id;
int cookie;
UT_hash_handle hh;
UT_hash_handle alth;
} example_user_t;
int main()
{
int i;
example_user_t *user, *tmp, *users=NULL, *altusers=NULL;
/* create elements */
for(i=0; i<1000; i++) {
user = (example_user_t*)malloc(sizeof(example_user_t));
if (user == NULL) {
exit(-1);
}
user->id = i;
user->cookie = i*i;
if (i<10) {
HASH_ADD_INT(users,id,user);
}
HASH_ADD(alth,altusers,id,sizeof(int),user);
}
/*
printf("hh items: %d, alth items: %d\n",
users->hh.tbl->num_items, users->alth.tbl->num_items);
printf("hh buckets: %d, alth buckets: %d\n",
users->hh.tbl->num_buckets, users->alth.tbl->num_buckets);
*/
i=9;
HASH_FIND_INT(users,&i,tmp);
printf("%d %s in hh\n", i, (tmp != NULL) ? "found" : "not found");
HASH_FIND(alth,altusers,&i,sizeof(int),tmp);
printf("%d %s in alth\n", i, (tmp != NULL) ? "found" : "not found");
i=10;
HASH_FIND_INT(users,&i,tmp);
printf("%d %s in hh\n", i, (tmp != NULL) ? "found" : "not found");
HASH_FIND(alth,altusers,&i,sizeof(int),tmp);
printf("%d %s in alth\n", i, (tmp != NULL) ? "found" : "not found");
HASH_CLEAR(hh, users);
HASH_CLEAR(alth, altusers);
return 0;
}

View File

@@ -1,51 +0,0 @@
ADRIAN
ARNOLDO
CARROLL
CARY
CHONG
CLIFTON
CODY
COLTON
CORNELL
DAMON
DANNIE
DARIO
DONN
DOUG
DOUGLAS
FREDERICK
FRITZ
GERALD
GUS
HARVEY
IRVING
ISAIAH
JARVIS
JOHN
KENTON
LAURENCE
LESTER
LINCOLN
LOWELL
NELSON
NEVILLE
NIGEL
NORMAND
ODIS
OMAR
ORLANDO
RAYMUNDO
REX
ROLANDO
RON
SHANE
TONEY
TRINIDAD
WALTER
WARNER
WARREN
WES
WILLARD
WILLIAM
WINFRED
XAVIER

View File

@@ -1,57 +0,0 @@
#include "uthash.h"
#include <stdlib.h> /* malloc */
#include <errno.h> /* perror */
#include <stdio.h> /* printf */
#define BUFLEN 20
#if 0
/* Print a message if the hash's no-expand flag is set. */
#undef uthash_noexpand_fyi
#undef uthash_expand_fyi
#define uthash_noexpand_fyi(tbl) printf("noexpand set\n");
#define uthash_expand_fyi(tbl) printf("hash expanded\n");
#endif
typedef struct name_rec {
char boy_name[BUFLEN];
UT_hash_handle hh;
} name_rec;
static int namecmp(void *_a, void *_b)
{
name_rec *a = (name_rec*)_a;
name_rec *b = (name_rec*)_b;
return strcmp(a->boy_name,b->boy_name);
}
int main()
{
name_rec *name, *names=NULL;
char linebuf[BUFLEN];
FILE *file;
file = fopen( "test11.dat", "r" );
if (file == NULL) {
perror("can't open: ");
exit(-1);
}
while (fgets(linebuf,BUFLEN,file) != NULL) {
name = (name_rec*)malloc(sizeof(name_rec));
if (name == NULL) {
exit(-1);
}
strcpy(name->boy_name, linebuf);
HASH_ADD_STR(names,boy_name,name);
}
fclose(file);
HASH_SORT(names,namecmp);
for(name=names; name!=NULL; name=(name_rec*)(name->hh.next)) {
printf("%s",name->boy_name);
}
return 0;
}

View File

@@ -1,51 +0,0 @@
JOHN
WILLIAM
WALTER
DOUGLAS
GERALD
FREDERICK
WARREN
SHANE
LESTER
RON
HARVEY
ADRIAN
CODY
NELSON
CLIFTON
WILLARD
DOUG
ORLANDO
REX
OMAR
DAMON
LOWELL
IRVING
CARROLL
LAURENCE
ROLANDO
CARY
XAVIER
ISAIAH
GUS
JARVIS
WINFRED
RAYMUNDO
LINCOLN
CORNELL
NIGEL
NORMAND
FRITZ
DONN
TRINIDAD
ODIS
DANNIE
DARIO
KENTON
CHONG
NEVILLE
TONEY
WARNER
WES
COLTON
ARNOLDO

View File

@@ -1,20 +0,0 @@
added bob (id 0)
added jack (id 1)
added gary (id 2)
added ty (id 3)
added bo (id 4)
added phil (id 5)
added art (id 6)
added gil (id 7)
added buck (id 8)
added ted (id 9)
found bob (id 0)
found jack (id 1)
found gary (id 2)
found ty (id 3)
found bo (id 4)
found phil (id 5)
found art (id 6)
found gil (id 7)
found buck (id 8)
found ted (id 9)

View File

@@ -1,40 +0,0 @@
#include "uthash.h"
#include <stdio.h>
#include <stdlib.h> /* malloc */
typedef struct person_t {
char first_name[10];
int id;
UT_hash_handle hh;
} person_t;
int main()
{
person_t *people=NULL, *person;
const char **name;
const char * names[] = { "bob", "jack", "gary", "ty", "bo", "phil", "art",
"gil", "buck", "ted", NULL
};
int id=0;
for(name=names; *name != NULL; name++) {
person = (person_t*)malloc(sizeof(person_t));
if (person == NULL) {
exit(-1);
}
strcpy(person->first_name, *name);
person->id = id++;
HASH_ADD_STR(people,first_name,person);
printf("added %s (id %d)\n", person->first_name, person->id);
}
for(name=names; *name != NULL; name++) {
HASH_FIND_STR(people,*name,person);
if (person != NULL) {
printf("found %s (id %d)\n", person->first_name, person->id);
} else {
printf("failed to find %s\n", *name);
}
}
return 0;
}

View File

@@ -1,5 +0,0 @@
id 9, following prev...
id 7, following prev...
id 5, following prev...
id 3, following prev...
id 1, following prev...

Some files were not shown because too many files have changed in this diff Show More