├── .github └── workflows │ ├── ci.yml │ └── hexpm-release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── c_src ├── mqtree.c └── uthash.h ├── configure ├── configure.ac ├── rebar ├── rebar.config ├── rebar.config.script ├── src ├── mqtree.app.src └── mqtree.erl ├── test └── mqtree_test.erl └── vars.config.in /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | tests: 8 | name: Tests 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | otp: ['20', '25', '26', '27', '28'] 13 | runs-on: ubuntu-24.04 14 | container: 15 | image: public.ecr.aws/docker/library/erlang:${{ matrix.otp }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - run: ./configure --enable-gcov 19 | - run: rebar3 compile 20 | - run: rebar3 xref 21 | - run: rebar3 dialyzer 22 | - run: rebar3 eunit -v 23 | - run: rebar3 eunit -v 24 | - name: Send to Coveralls 25 | if: matrix.otp == 27 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | run: | 29 | apt-get -qq update 30 | apt-get -qq install pipx 31 | pipx install cpp-coveralls 32 | /github/home/.local/bin/cpp-coveralls -b `pwd` --verbose --gcov-options '\-lp' --dump c.json 33 | ADDJSONFILE=c.json COVERALLS=true rebar3 as test coveralls send 34 | curl -v -k https://coveralls.io/webhook \ 35 | --header "Content-Type: application/json" \ 36 | --data '{"repo_name":"$GITHUB_REPOSITORY", 37 | "repo_token":"$GITHUB_TOKEN", 38 | "payload":{"build_num":$GITHUB_RUN_ID, 39 | "status":"done"}}' 40 | -------------------------------------------------------------------------------- /.github/workflows/hexpm-release.yml: -------------------------------------------------------------------------------- 1 | name: Hex 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-24.04 11 | container: 12 | image: public.ecr.aws/docker/library/erlang 13 | steps: 14 | - name: Check out 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup rebar3 hex 18 | run: | 19 | mkdir -p ~/.config/rebar3/ 20 | echo "{plugins, [rebar3_hex]}." > ~/.config/rebar3/rebar.config 21 | 22 | - name: Publish to hex.pm 23 | run: DIAGNOSTIC=1 rebar3 hex publish --repo hexpm --yes 24 | env: 25 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | .eunit 4 | .rebar 5 | _build 6 | autom4te.cache 7 | c_src/*.d 8 | c_src/*.gcda 9 | c_src/*.gcno 10 | c_src/*.o 11 | config.log 12 | config.status 13 | deps 14 | ebin 15 | priv 16 | rebar.lock 17 | vars.config 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 1.0.18 2 | 3 | * Updating p1_utils to version 1.0.27. 4 | 5 | # Version 1.0.17 6 | 7 | * Updating p1_utils to version 1.0.26. 8 | 9 | # Version 1.0.16 10 | 11 | * Always use enif_free to dealocate memory 12 | 13 | # Version 1.0.15 14 | 15 | * Updating p1_utils to version 1.0.25. 16 | 17 | # Version 1.0.14 18 | 19 | * Updating p1_utils to version 1.0.23. 20 | * Switch from using Travis to Github Actions as CI 21 | 22 | # Version 1.0.13 23 | 24 | * Updating p1_utils to version 1.0.22. 25 | 26 | # Version 1.0.12 27 | 28 | * Add missing applications to mqtree.app 29 | 30 | # Version 1.0.11 31 | 32 | * Updating p1_utils to version 1.0.21. 33 | * Don't link with ssl/crypto libs 34 | 35 | # Version 1.0.10 36 | 37 | * Updating p1_utils to version 1.0.20. 38 | 39 | # Version 1.0.9 40 | 41 | * Copy recent rebar.config.script from fast_tls 42 | * Add support for Travis and Erlang/OTP 23.0 43 | 44 | # Version 1.0.8 45 | 46 | * Updating p1_utils to version 1.0.19. 47 | 48 | # Version 1.0.7 49 | 50 | * Updating p1_utils to version 1.0.18. 51 | * Update copyright year 52 | 53 | # Version 1.0.6 54 | 55 | * Updating p1_utils to version 1.0.17. 56 | * Fix repo url in README 57 | 58 | # Version 1.0.5 59 | 60 | * Improve dialyzer handling 61 | 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR ?= ./rebar 2 | 3 | IS_REBAR3:=$(shell expr `$(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}'` '>=' 3) 4 | 5 | all: src 6 | 7 | src: 8 | $(REBAR) get-deps 9 | $(REBAR) compile 10 | 11 | clean: 12 | $(REBAR) clean 13 | 14 | distclean: clean 15 | rm -f config.status 16 | rm -f config.log 17 | rm -rf autom4te.cache 18 | rm -rf _build 19 | rm -rf deps 20 | rm -rf ebin 21 | rm -f rebar.lock 22 | rm -f test/*.beam 23 | rm -rf priv 24 | rm -f vars.config 25 | rm -f compile_commands.json 26 | rm -rf dialyzer 27 | 28 | ifeq "$(IS_REBAR3)" "1" 29 | test: 30 | $(REBAR) eunit -v 31 | else 32 | test: all 33 | mkdir -p .eunit/priv/lib 34 | cp priv/lib/mqtree.* .eunit/priv/lib/ 35 | $(REBAR) -v skip_deps=true eunit 36 | endif 37 | 38 | xref: all 39 | $(REBAR) xref 40 | 41 | ifeq "$(IS_REBAR3)" "1" 42 | dialyzer: 43 | $(REBAR) dialyzer 44 | else 45 | deps := $(wildcard deps/*/ebin) 46 | 47 | dialyzer/erlang.plt: 48 | @mkdir -p dialyzer 49 | @dialyzer --build_plt --output_plt dialyzer/erlang.plt \ 50 | -o dialyzer/erlang.log --apps kernel stdlib erts; \ 51 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 52 | 53 | dialyzer/deps.plt: 54 | @mkdir -p dialyzer 55 | @dialyzer --build_plt --output_plt dialyzer/deps.plt \ 56 | -o dialyzer/deps.log $(deps); \ 57 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 58 | 59 | dialyzer/mqtree.plt: 60 | @mkdir -p dialyzer 61 | @dialyzer --build_plt --output_plt dialyzer/mqtree.plt \ 62 | -o dialyzer/mqtree.log ebin; \ 63 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 64 | 65 | erlang_plt: dialyzer/erlang.plt 66 | @dialyzer --plt dialyzer/erlang.plt --check_plt -o dialyzer/erlang.log; \ 67 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 68 | 69 | deps_plt: dialyzer/deps.plt 70 | @dialyzer --plt dialyzer/deps.plt --check_plt -o dialyzer/deps.log; \ 71 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 72 | 73 | mqtree_plt: dialyzer/mqtree.plt 74 | @dialyzer --plt dialyzer/mqtree.plt --check_plt -o dialyzer/mqtree.log; \ 75 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 76 | 77 | dialyzer: erlang_plt deps_plt mqtree_plt 78 | @dialyzer --plts dialyzer/*.plt --no_check_plt \ 79 | --get_warnings -o dialyzer/error.log ebin; \ 80 | status=$$? ; if [ $$status -ne 2 ]; then exit $$status; else exit 0; fi 81 | endif 82 | 83 | check-syntax: 84 | gcc -o nul -S ${CHK_SOURCES} 85 | 86 | .PHONY: clean src test all dialyzer erlang_plt deps_plt mqtree_plt 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mqtree: Index tree for MQTT topic filters 2 | ==================================================== 3 | 4 | [![CI](https://github.com/processone/mqtree/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/processone/mqtree/actions/workflows/ci.yml) 5 | [![Coverage Status](https://coveralls.io/repos/processone/mqtree/badge.svg?branch=master&service=github)](https://coveralls.io/github/processone/mqtree?branch=master) 6 | [![Hex version](https://img.shields.io/hexpm/v/mqtree.svg "Hex version")](https://hex.pm/packages/mqtree) 7 | 8 | mqtree is an Erlang NIF implementation of N-ary tree to keep MQTT 9 | topic filters for efficient matching. 10 | 11 | # System requirements 12 | 13 | To compile mqtree you need: 14 | 15 | - GNU Make. 16 | - GCC. 17 | - Erlang/OTP 17.5 or higher. 18 | 19 | # Compiling 20 | 21 | ``` 22 | $ git clone https://github.com/processone/mqtree.git 23 | $ cd mqtree 24 | $ make 25 | ``` 26 | 27 | # API 28 | 29 | ## new/0 30 | ```erlang 31 | -spec new() -> tree(). 32 | ``` 33 | Creates new tree. The tree is mutable just like ETS, so there is 34 | no need to keep its updated version between calls. 35 | The created tree gets destroyed when it's garbage collected. 36 | 37 | Complexity: `O(1)`. 38 | 39 | **NOTE**: a registered tree (see [register/2](#register2)) is 40 | not a subject for garbage collection until [unregister/1](#unregister1) 41 | is called **explicitly**. 42 | 43 | ## insert/2 44 | ```erlang 45 | -spec insert(Tree :: tree(), Filter :: iodata()) -> ok. 46 | ``` 47 | Inserts `Filter` into `Tree` and increases its reference counter. 48 | The reference counter is increased every time when the same 49 | filter is inserted into the tree. The reference counter is decreased 50 | when the filter is deleted, see [delete/2](#delete2). 51 | 52 | Complexity: `O(H)` where `H` is the number of slashes (`/`) in `Filter`. 53 | 54 | **NOTE**: no checks are performed on the filter being inserted: 55 | it's up to the caller to check if the filter conforms to the MQTT 56 | specification. 57 | 58 | ## delete/2 59 | ```erlang 60 | -spec delete(Tree :: tree(), Filter :: iodata()) -> ok. 61 | ``` 62 | Deletes `Filter` from `Tree` and decreases its reference counter. 63 | Nothing is done if the filter is not found in the tree. 64 | 65 | Complexity: `O(H)` where `H` is the number of slashes (`/`) in `Filter`. 66 | 67 | **NOTE**: no checks are performed on the filter being deleted: 68 | it's up to the caller to check if the filter conforms to the MQTT 69 | specification. 70 | 71 | ## match/2 72 | ```erlang 73 | -spec match(Tree :: tree(), Path :: iodata()) -> [binary()]. 74 | ``` 75 | Finds filters in `Tree` matching `Path` according to the MQTT 76 | specification. 77 | 78 | Complexity: `O(2^H)` worst case, where `H` is the number of slashes (`/`) in `Path`. 79 | Note that the worst case complexity is only achieved when an attacker forces to 80 | store in the tree a massive amount of filters containing `+` meta-symbol. The 81 | obvious protection is to restrict the filter depth. Another approach is to 82 | make filter "deduplication" during subscription registration, e.g. filters 83 | `a/+`, `+/b` and `+/+` should be "merged" into single `+/+`. 84 | 85 | **NOTE**: no checks are performed on the path being matched: 86 | it's up to the caller to check if the path conforms to the MQTT 87 | specification. 88 | 89 | **NOTE**: any path starting with `$` won't match filters starting with 90 | `+` or `#`. This is in accordance with the MQTT specification. 91 | 92 | ## refc/2 93 | ```erlang 94 | -spec refc(Tree :: tree(), Filter :: iodata()) -> non_neg_intger(). 95 | ``` 96 | Returns the reference counter of `Filter` in `Tree`. In particular, 97 | zero (0) is returned if the filter is not found in the tree. 98 | 99 | Complexity: `O(H)` where `H` is the number of slashes (`/`) in `Filter`. 100 | 101 | **NOTE**: no checks are performed on the filter being searched: 102 | it's up to the caller to check if the filter conforms to the MQTT 103 | specification. 104 | 105 | ## clear/1 106 | ```erlang 107 | -spec clear(Tree :: tree()) -> ok. 108 | ``` 109 | Deletes all filters from `Tree`. 110 | 111 | Complexity: `O(N)` where `N` is the number of filters in the tree. 112 | 113 | ## size/1 114 | ```erlang 115 | -spec size(Tree :: tree()) -> non_neg_integer(). 116 | ``` 117 | Returns the size of `Tree`. That is, the number of filters in the 118 | tree (irrespective of their reference counters). 119 | 120 | Complexity: `O(N)` where `N` is the number of filters in the tree. 121 | 122 | ## is_empty/1 123 | ```erlang 124 | -spec is_empty(Tree :: tree()) -> boolean(). 125 | ``` 126 | Returns `true` if `Tree` holds no filters. Returns `false` otherwise. 127 | 128 | Complexity: `O(1)`. 129 | 130 | ## register/2 131 | ```erlang 132 | -spec register(RegName :: atom(), Tree :: tree()) -> ok. 133 | ``` 134 | Associates `RegName` with `Tree`. The tree is then available via call 135 | to [whereis/1](#whereis1). Fails with `badarg` exception if: 136 | 137 | - `RegName` is already in use (even by the tree being registered) 138 | - `RegName` is atom `undefined` 139 | - Either `RegName` or `Tree` has invalid type 140 | 141 | It is safe to register already registered tree to another name. In this 142 | case the old name will be freed automatically. 143 | 144 | Complexity: `O(1)`. 145 | 146 | **NOTE**: a registered tree is not a subject for garbage collection. 147 | You must call [unregister/1](#unregister1) **explicitly** if you want 148 | the tree to be freed by garbage collector. 149 | 150 | ## unregister/1 151 | ```erlang 152 | -spec unregister(RegName :: atom()) -> ok. 153 | ``` 154 | Removes the registered name `RegName` associated with a tree. 155 | Fails with `badarg` exception if `RegName` is not a registered name. 156 | 157 | Complexity: `O(1)`. 158 | 159 | ## whereis/1 160 | ```erlang 161 | -spec whereis(RegName :: atom()) -> Tree :: tree() | undefined. 162 | ``` 163 | Returns `Tree` with registered name `RegName`. Returns `undefined` otherwise. 164 | 165 | Complexity: `O(1)`. 166 | -------------------------------------------------------------------------------- /c_src/mqtree.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @author Evgeny Khramtsov 3 | * @copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include "uthash.h" 23 | 24 | void __free(void *ptr, size_t size) { 25 | enif_free(ptr); 26 | } 27 | 28 | #undef uthash_malloc 29 | #undef uthash_free 30 | #define uthash_malloc enif_alloc 31 | #define uthash_free __free 32 | 33 | /**************************************************************** 34 | * Structures/Globals definitions * 35 | ****************************************************************/ 36 | typedef struct __tree_t { 37 | char *key; 38 | char *val; 39 | int refc; 40 | struct __tree_t *sub; 41 | UT_hash_handle hh; 42 | } tree_t; 43 | 44 | typedef struct { 45 | tree_t *tree; 46 | char *name; 47 | ErlNifRWLock *lock; 48 | } state_t; 49 | 50 | typedef struct { 51 | char *name; 52 | state_t *state; 53 | UT_hash_handle hh; 54 | } registry_t; 55 | 56 | static ErlNifResourceType *tree_state_t = NULL; 57 | static registry_t *registry = NULL; 58 | static ErlNifRWLock *registry_lock = NULL; 59 | 60 | /**************************************************************** 61 | * MQTT Tree Manipulation * 62 | ****************************************************************/ 63 | tree_t *tree_new(char *key, size_t len) { 64 | tree_t *tree = enif_alloc(sizeof(tree_t)); 65 | if (tree) { 66 | memset(tree, 0, sizeof(tree_t)); 67 | if (key && len) { 68 | tree->key = enif_alloc(len); 69 | if (tree->key) { 70 | memcpy(tree->key, key, len); 71 | } else { 72 | enif_free(tree); 73 | tree = NULL; 74 | } 75 | } 76 | } 77 | return tree; 78 | } 79 | 80 | void tree_free(tree_t *t) { 81 | tree_t *found, *iter; 82 | if (t) { 83 | enif_free(t->key); 84 | enif_free(t->val); 85 | HASH_ITER(hh, t->sub, found, iter) { 86 | HASH_DEL(t->sub, found); 87 | tree_free(found); 88 | } 89 | memset(t, 0, sizeof(tree_t)); 90 | enif_free(t); 91 | } 92 | } 93 | 94 | void tree_clear(tree_t *root) { 95 | tree_t *found, *iter; 96 | HASH_ITER(hh, root->sub, found, iter) { 97 | HASH_DEL(root->sub, found); 98 | tree_free(found); 99 | } 100 | } 101 | 102 | int tree_add(tree_t *root, char *path, size_t size) { 103 | int i = 0; 104 | size_t len; 105 | tree_t *t = root; 106 | tree_t *found, *new; 107 | 108 | while (i<=size) { 109 | len = strlen(path+i) + 1; 110 | HASH_FIND_STR(t->sub, path+i, found); 111 | if (found) { 112 | i += len; 113 | t = found; 114 | } else { 115 | new = tree_new(path+i, len); 116 | if (new) { 117 | HASH_ADD_STR(t->sub, key, new); 118 | i += len; 119 | t = new; 120 | } else 121 | return ENOMEM; 122 | } 123 | } 124 | 125 | if (!t->val) { 126 | t->val = enif_alloc(size+1); 127 | if (t->val) { 128 | t->val[size] = 0; 129 | for (i=0; ival[i] = c ? c : '/'; 132 | } 133 | } else 134 | return ENOMEM; 135 | } 136 | t->refc++; 137 | return 0; 138 | } 139 | 140 | int tree_del(tree_t *root, char *path, size_t i, size_t size) { 141 | tree_t *found; 142 | 143 | if (i<=size) { 144 | HASH_FIND_STR(root->sub, path+i, found); 145 | if (found) { 146 | i += strlen(path+i) + 1; 147 | int deleted = tree_del(found, path, i, size); 148 | if (deleted) { 149 | HASH_DEL(root->sub, found); 150 | tree_free(found); 151 | } 152 | } 153 | } else if (root->refc) { 154 | root->refc--; 155 | if (!root->refc) { 156 | enif_free(root->val); 157 | root->val = NULL; 158 | } 159 | } 160 | 161 | return !root->refc && !root->sub; 162 | } 163 | 164 | void tree_size(tree_t *tree, size_t *size) { 165 | tree_t *found, *iter; 166 | 167 | HASH_ITER(hh, tree->sub, found, iter) { 168 | if (found->refc) (*size)++; 169 | tree_size(found, size); 170 | } 171 | } 172 | 173 | int tree_refc(tree_t *tree, char *path, size_t i, size_t size) { 174 | tree_t *found; 175 | 176 | if (i<=size) { 177 | HASH_FIND_STR(tree->sub, path+i, found); 178 | if (found) { 179 | i += strlen(path+i) + 1; 180 | return tree_refc(found, path, i, size); 181 | } else { 182 | return 0; 183 | } 184 | } else 185 | return tree->refc; 186 | } 187 | 188 | /**************************************************************** 189 | * Registration * 190 | ****************************************************************/ 191 | void delete_registry_entry(registry_t *entry) { 192 | /* registry_lock must be RW-locked! */ 193 | HASH_DEL(registry, entry); 194 | entry->state->name = NULL; 195 | enif_release_resource(entry->state); 196 | enif_free(entry->name); 197 | enif_free(entry); 198 | } 199 | 200 | int register_tree(char *name, state_t *state) { 201 | registry_t *entry, *found; 202 | 203 | entry = enif_alloc(sizeof(registry_t)); 204 | if (!entry) return ENOMEM; 205 | 206 | entry->name = enif_alloc(strlen(name) + 1); 207 | if (!entry->name) { 208 | enif_free(entry); 209 | return ENOMEM; 210 | } 211 | 212 | entry->state = state; 213 | strcpy(entry->name, name); 214 | enif_rwlock_rwlock(registry_lock); 215 | HASH_FIND_STR(registry, name, found); 216 | if (found) { 217 | enif_rwlock_rwunlock(registry_lock); 218 | enif_free(entry->name); 219 | enif_free(entry); 220 | return EINVAL; 221 | } else { 222 | if (state->name) { 223 | /* Unregistering previously registered name */ 224 | HASH_FIND_STR(registry, state->name, found); 225 | if (found) 226 | delete_registry_entry(found); 227 | } 228 | enif_keep_resource(state); 229 | HASH_ADD_STR(registry, name, entry); 230 | state->name = entry->name; 231 | enif_rwlock_rwunlock(registry_lock); 232 | return 0; 233 | } 234 | } 235 | 236 | int unregister_tree(char *name) { 237 | registry_t *entry; 238 | int ret; 239 | 240 | enif_rwlock_rwlock(registry_lock); 241 | HASH_FIND_STR(registry, name, entry); 242 | if (entry) { 243 | delete_registry_entry(entry); 244 | ret = 0; 245 | } else { 246 | ret = EINVAL; 247 | } 248 | enif_rwlock_rwunlock(registry_lock); 249 | 250 | return ret; 251 | } 252 | 253 | /**************************************************************** 254 | * NIF helpers * 255 | ****************************************************************/ 256 | static ERL_NIF_TERM cons(ErlNifEnv *env, char *str, ERL_NIF_TERM tail) 257 | { 258 | if (str) { 259 | size_t len = strlen(str); 260 | ERL_NIF_TERM head; 261 | unsigned char *buf = enif_make_new_binary(env, len, &head); 262 | if (buf) { 263 | memcpy(buf, str, len); 264 | return enif_make_list_cell(env, head, tail); 265 | } 266 | } 267 | return tail; 268 | } 269 | 270 | static void match(ErlNifEnv *env, tree_t *root, 271 | char *path, size_t i, size_t size, ERL_NIF_TERM *acc) 272 | { 273 | tree_t *found; 274 | size_t len = 0; 275 | 276 | if (i<=size) { 277 | HASH_FIND_STR(root->sub, path+i, found); 278 | if (found) { 279 | len = strlen(path+i) + 1; 280 | match(env, found, path, i+len, size, acc); 281 | }; 282 | if (i || path[0] != '$') { 283 | HASH_FIND_STR(root->sub, "+", found); 284 | if (found) { 285 | len = strlen(path+i) + 1; 286 | match(env, found, path, i+len, size, acc); 287 | } 288 | HASH_FIND_STR(root->sub, "#", found); 289 | if (found) { 290 | *acc = cons(env, found->val, *acc); 291 | } 292 | } 293 | } else { 294 | *acc = cons(env, root->val, *acc); 295 | HASH_FIND_STR(root->sub, "#", found); 296 | if (found) 297 | *acc = cons(env, found->val, *acc); 298 | } 299 | } 300 | 301 | static void to_list(ErlNifEnv *env, tree_t *root, ERL_NIF_TERM *acc) 302 | { 303 | tree_t *found, *iter; 304 | 305 | HASH_ITER(hh, root->sub, found, iter) { 306 | if (found->val) { 307 | size_t len = strlen(found->val); 308 | ERL_NIF_TERM refc = enif_make_int(env, found->refc); 309 | ERL_NIF_TERM val; 310 | unsigned char *buf = enif_make_new_binary(env, len, &val); 311 | if (buf) { 312 | memcpy(buf, found->val, len); 313 | *acc = enif_make_list_cell(env, enif_make_tuple2(env, val, refc), *acc); 314 | } 315 | }; 316 | to_list(env, found, acc); 317 | } 318 | } 319 | 320 | static ERL_NIF_TERM dump(ErlNifEnv *env, tree_t *tree) 321 | { 322 | tree_t *found, *iter; 323 | ERL_NIF_TERM tail, head; 324 | 325 | tail = enif_make_list(env, 0); 326 | HASH_ITER(hh, tree->sub, found, iter) { 327 | head = dump(env, found); 328 | tail = enif_make_list_cell(env, head, tail); 329 | } 330 | if (tree->key) { 331 | ERL_NIF_TERM part, path; 332 | part = enif_make_string(env, tree->key, ERL_NIF_LATIN1); 333 | if (tree->val) 334 | path = enif_make_string(env, tree->val, ERL_NIF_LATIN1); 335 | else 336 | path = enif_make_atom(env, "none"); 337 | return enif_make_tuple4(env, part, path, enif_make_int(env, tree->refc), tail); 338 | } else 339 | return tail; 340 | } 341 | 342 | static ERL_NIF_TERM raise(ErlNifEnv *env, int err) 343 | { 344 | switch (err) { 345 | case ENOMEM: 346 | return enif_raise_exception(env, enif_make_atom(env, "enomem")); 347 | default: 348 | return enif_make_badarg(env); 349 | } 350 | } 351 | 352 | void prep_path(char *path, ErlNifBinary *bin) { 353 | int i; 354 | unsigned char c; 355 | path[bin->size] = 0; 356 | for (i=0; isize; i++) { 357 | c = bin->data[i]; 358 | path[i] = (c == '/') ? 0 : c; 359 | } 360 | } 361 | 362 | /**************************************************************** 363 | * Constructors/Destructors * 364 | ****************************************************************/ 365 | static state_t *init_tree_state(ErlNifEnv *env) { 366 | state_t *state = enif_alloc_resource(tree_state_t, sizeof(state_t)); 367 | if (state) { 368 | memset(state, 0, sizeof(state_t)); 369 | state->tree = tree_new(NULL, 0); 370 | state->lock = enif_rwlock_create("mqtree_lock"); 371 | if (state->tree && state->lock) 372 | return state; 373 | else 374 | enif_release_resource(state); 375 | } 376 | return NULL; 377 | } 378 | 379 | static void destroy_tree_state(ErlNifEnv *env, void *data) { 380 | state_t *state = (state_t *) data; 381 | if (state) { 382 | tree_free(state->tree); 383 | if (state->lock) enif_rwlock_destroy(state->lock); 384 | } 385 | memset(state, 0, sizeof(state_t)); 386 | } 387 | 388 | /**************************************************************** 389 | * NIF definitions * 390 | ****************************************************************/ 391 | static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM max) { 392 | registry_lock = enif_rwlock_create("mqtree_registry"); 393 | if (registry_lock) { 394 | ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; 395 | tree_state_t = enif_open_resource_type(env, NULL, "mqtree_state", 396 | destroy_tree_state, 397 | flags, NULL); 398 | return 0; 399 | } 400 | return ENOMEM; 401 | } 402 | 403 | static void unload(ErlNifEnv* env, void* priv) { 404 | if (registry_lock) { 405 | enif_rwlock_destroy(registry_lock); 406 | registry_lock = NULL; 407 | } 408 | } 409 | 410 | static ERL_NIF_TERM new_0(ErlNifEnv* env, int argc, 411 | const ERL_NIF_TERM argv[]) 412 | { 413 | ERL_NIF_TERM result; 414 | state_t *state = init_tree_state(env); 415 | if (state) { 416 | result = enif_make_resource(env, state); 417 | enif_release_resource(state); 418 | } else 419 | result = raise(env, ENOMEM); 420 | 421 | return result; 422 | } 423 | 424 | static ERL_NIF_TERM insert_2(ErlNifEnv* env, int argc, 425 | const ERL_NIF_TERM argv[]) 426 | { 427 | state_t *state; 428 | ErlNifBinary path_bin; 429 | 430 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) || 431 | !enif_inspect_iolist_as_binary(env, argv[1], &path_bin)) 432 | return raise(env, EINVAL); 433 | 434 | if (!path_bin.size) 435 | return enif_make_atom(env, "ok"); 436 | 437 | char path[path_bin.size+1]; 438 | prep_path(path, &path_bin); 439 | enif_rwlock_rwlock(state->lock); 440 | int ret = tree_add(state->tree, path, path_bin.size); 441 | enif_rwlock_rwunlock(state->lock); 442 | 443 | if (!ret) 444 | return enif_make_atom(env, "ok"); 445 | else 446 | return raise(env, ret); 447 | } 448 | 449 | static ERL_NIF_TERM delete_2(ErlNifEnv* env, int argc, 450 | const ERL_NIF_TERM argv[]) 451 | { 452 | state_t *state; 453 | ErlNifBinary path_bin; 454 | 455 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) || 456 | !enif_inspect_iolist_as_binary(env, argv[1], &path_bin)) 457 | return raise(env, EINVAL); 458 | 459 | if (!path_bin.size) 460 | return enif_make_atom(env, "ok"); 461 | 462 | char path[path_bin.size+1]; 463 | prep_path(path, &path_bin); 464 | enif_rwlock_rwlock(state->lock); 465 | tree_del(state->tree, path, 0, path_bin.size); 466 | enif_rwlock_rwunlock(state->lock); 467 | 468 | return enif_make_atom(env, "ok"); 469 | } 470 | 471 | static ERL_NIF_TERM match_2(ErlNifEnv* env, int argc, 472 | const ERL_NIF_TERM argv[]) 473 | { 474 | state_t *state; 475 | ErlNifBinary path_bin; 476 | ERL_NIF_TERM result = enif_make_list(env, 0); 477 | 478 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) || 479 | !enif_inspect_iolist_as_binary(env, argv[1], &path_bin)) 480 | return raise(env, EINVAL); 481 | 482 | if (!path_bin.size) 483 | return result; 484 | 485 | char path[path_bin.size+1]; 486 | prep_path(path, &path_bin); 487 | enif_rwlock_rlock(state->lock); 488 | match(env, state->tree, path, 0, path_bin.size, &result); 489 | enif_rwlock_runlock(state->lock); 490 | 491 | return result; 492 | } 493 | 494 | static ERL_NIF_TERM refc_2(ErlNifEnv* env, int argc, 495 | const ERL_NIF_TERM argv[]) 496 | { 497 | state_t *state; 498 | ErlNifBinary path_bin; 499 | 500 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state) || 501 | !enif_inspect_iolist_as_binary(env, argv[1], &path_bin)) 502 | return raise(env, EINVAL); 503 | 504 | if (!path_bin.size) 505 | return enif_make_int(env, 0); 506 | 507 | char path[path_bin.size+1]; 508 | prep_path(path, &path_bin); 509 | enif_rwlock_rlock(state->lock); 510 | int refc = tree_refc(state->tree, path, 0, path_bin.size); 511 | enif_rwlock_runlock(state->lock); 512 | 513 | return enif_make_int(env, refc); 514 | } 515 | 516 | static ERL_NIF_TERM clear_1(ErlNifEnv* env, int argc, 517 | const ERL_NIF_TERM argv[]) 518 | { 519 | state_t *state; 520 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state)) 521 | return raise(env, EINVAL); 522 | 523 | enif_rwlock_rwlock(state->lock); 524 | tree_clear(state->tree); 525 | enif_rwlock_rwunlock(state->lock); 526 | 527 | return enif_make_atom(env, "ok"); 528 | } 529 | 530 | static ERL_NIF_TERM size_1(ErlNifEnv* env, int argc, 531 | const ERL_NIF_TERM argv[]) 532 | { 533 | state_t *state; 534 | size_t size = 0; 535 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state)) 536 | return raise(env, EINVAL); 537 | 538 | enif_rwlock_rlock(state->lock); 539 | tree_size(state->tree, &size); 540 | enif_rwlock_runlock(state->lock); 541 | 542 | return enif_make_uint64(env, (ErlNifUInt64) size); 543 | } 544 | 545 | static ERL_NIF_TERM is_empty_1(ErlNifEnv* env, int argc, 546 | const ERL_NIF_TERM argv[]) 547 | { 548 | state_t *state; 549 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state)) 550 | return raise(env, EINVAL); 551 | 552 | enif_rwlock_rlock(state->lock); 553 | char *ret = state->tree->sub ? "false" : "true"; 554 | enif_rwlock_runlock(state->lock); 555 | 556 | return enif_make_atom(env, ret); 557 | } 558 | 559 | static ERL_NIF_TERM to_list_1(ErlNifEnv* env, int argc, 560 | const ERL_NIF_TERM argv[]) 561 | { 562 | state_t *state; 563 | ERL_NIF_TERM result = enif_make_list(env, 0); 564 | 565 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state)) 566 | return raise(env, EINVAL); 567 | 568 | enif_rwlock_rlock(state->lock); 569 | to_list(env, state->tree, &result); 570 | enif_rwlock_runlock(state->lock); 571 | 572 | return result; 573 | } 574 | 575 | static ERL_NIF_TERM dump_1(ErlNifEnv* env, int argc, 576 | const ERL_NIF_TERM argv[]) 577 | { 578 | state_t *state; 579 | if (!enif_get_resource(env, argv[0], tree_state_t, (void *) &state)) 580 | return raise(env, EINVAL); 581 | 582 | enif_rwlock_rlock(state->lock); 583 | ERL_NIF_TERM result = dump(env, state->tree); 584 | enif_rwlock_runlock(state->lock); 585 | 586 | return result; 587 | } 588 | 589 | static ERL_NIF_TERM register_2(ErlNifEnv* env, int argc, 590 | const ERL_NIF_TERM argv[]) 591 | { 592 | state_t *state; 593 | unsigned int len; 594 | int ret; 595 | 596 | if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1) || 597 | !enif_get_resource(env, argv[1], tree_state_t, (void *) &state)) 598 | return raise(env, EINVAL); 599 | 600 | char name[len+1]; 601 | enif_get_atom(env, argv[0], name, len+1, ERL_NIF_LATIN1); 602 | if (!strcmp(name, "undefined")) 603 | return raise(env, EINVAL); 604 | 605 | ret = register_tree(name, state); 606 | if (ret) 607 | return raise(env, ret); 608 | else 609 | return enif_make_atom(env, "ok"); 610 | } 611 | 612 | static ERL_NIF_TERM unregister_1(ErlNifEnv* env, int argc, 613 | const ERL_NIF_TERM argv[]) 614 | { 615 | unsigned int len; 616 | int ret; 617 | 618 | if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1)) 619 | return raise(env, EINVAL); 620 | 621 | char name[len+1]; 622 | enif_get_atom(env, argv[0], name, len+1, ERL_NIF_LATIN1); 623 | ret = unregister_tree(name); 624 | if (ret) 625 | return raise(env, ret); 626 | else 627 | return enif_make_atom(env, "ok"); 628 | } 629 | 630 | static ERL_NIF_TERM whereis_1(ErlNifEnv* env, int argc, 631 | const ERL_NIF_TERM argv[]) 632 | { 633 | unsigned int len; 634 | registry_t *entry; 635 | ERL_NIF_TERM result; 636 | 637 | if (!enif_get_atom_length(env, argv[0], &len, ERL_NIF_LATIN1)) 638 | return raise(env, EINVAL); 639 | 640 | char name[len+1]; 641 | enif_get_atom(env, argv[0], name, len+1, ERL_NIF_LATIN1); 642 | enif_rwlock_rlock(registry_lock); 643 | HASH_FIND_STR(registry, name, entry); 644 | if (entry) 645 | result = enif_make_resource(env, entry->state); 646 | else 647 | result = enif_make_atom(env, "undefined"); 648 | enif_rwlock_runlock(registry_lock); 649 | 650 | return result; 651 | } 652 | 653 | static ERL_NIF_TERM registered_0(ErlNifEnv* env, int argc, 654 | const ERL_NIF_TERM argv[]) 655 | { 656 | registry_t *entry, *iter; 657 | ERL_NIF_TERM result = enif_make_list(env, 0); 658 | 659 | enif_rwlock_rlock(registry_lock); 660 | HASH_ITER(hh, registry, entry, iter) { 661 | result = enif_make_list_cell(env, enif_make_atom(env, entry->name), result); 662 | } 663 | enif_rwlock_runlock(registry_lock); 664 | 665 | return result; 666 | } 667 | 668 | static ErlNifFunc nif_funcs[] = 669 | { 670 | {"new", 0, new_0}, 671 | {"insert", 2, insert_2}, 672 | {"delete", 2, delete_2}, 673 | {"match", 2, match_2}, 674 | {"refc", 2, refc_2}, 675 | {"clear", 1, clear_1}, 676 | {"size", 1, size_1}, 677 | {"is_empty", 1, is_empty_1}, 678 | {"to_list", 1, to_list_1}, 679 | {"dump", 1, dump_1}, 680 | {"register", 2, register_2}, 681 | {"unregister", 1, unregister_1}, 682 | {"whereis", 1, whereis_1}, 683 | {"registered", 0, registered_0} 684 | }; 685 | 686 | ERL_NIF_INIT(mqtree, nif_funcs, load, NULL, NULL, unload) 687 | -------------------------------------------------------------------------------- /c_src/uthash.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2017, Troy D. Hanson http://troydhanson.github.com/uthash/ 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 12 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 13 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 15 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 16 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 18 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 19 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | 24 | #ifndef UTHASH_H 25 | #define UTHASH_H 26 | 27 | #define UTHASH_VERSION 2.0.2 28 | 29 | #include /* memcmp,strlen */ 30 | #include /* ptrdiff_t */ 31 | #include /* exit() */ 32 | 33 | /* These macros use decltype or the earlier __typeof GNU extension. 34 | As decltype is only available in newer compilers (VS2010 or gcc 4.3+ 35 | when compiling c++ source) this code uses whatever method is needed 36 | or, for VS2008 where neither is available, uses casting workarounds. */ 37 | #if defined(_MSC_VER) /* MS compiler */ 38 | #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ 39 | #define DECLTYPE(x) (decltype(x)) 40 | #else /* VS2008 or older (or VS2010 in C mode) */ 41 | #define NO_DECLTYPE 42 | #define DECLTYPE(x) 43 | #endif 44 | #elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__) 45 | #define NO_DECLTYPE 46 | #define DECLTYPE(x) 47 | #else /* GNU, Sun and other compilers */ 48 | #define DECLTYPE(x) (__typeof(x)) 49 | #endif 50 | 51 | #ifdef NO_DECLTYPE 52 | #define DECLTYPE_ASSIGN(dst,src) \ 53 | do { \ 54 | char **_da_dst = (char**)(&(dst)); \ 55 | *_da_dst = (char*)(src); \ 56 | } while (0) 57 | #else 58 | #define DECLTYPE_ASSIGN(dst,src) \ 59 | do { \ 60 | (dst) = DECLTYPE(dst)(src); \ 61 | } while (0) 62 | #endif 63 | 64 | /* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */ 65 | #if defined(_WIN32) 66 | #if defined(_MSC_VER) && _MSC_VER >= 1600 67 | #include 68 | #elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__) 69 | #include 70 | #else 71 | typedef unsigned int uint32_t; 72 | typedef unsigned char uint8_t; 73 | #endif 74 | #elif defined(__GNUC__) && !defined(__VXWORKS__) 75 | #include 76 | #else 77 | typedef unsigned int uint32_t; 78 | typedef unsigned char uint8_t; 79 | #endif 80 | 81 | #ifndef uthash_fatal 82 | #define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ 83 | #endif 84 | #ifndef uthash_malloc 85 | #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ 86 | #endif 87 | #ifndef uthash_free 88 | #define uthash_free(ptr,sz) free(ptr) /* free fcn */ 89 | #endif 90 | #ifndef uthash_strlen 91 | #define uthash_strlen(s) strlen(s) 92 | #endif 93 | #ifndef uthash_memcmp 94 | #define uthash_memcmp(a,b,n) memcmp(a,b,n) 95 | #endif 96 | 97 | #ifndef uthash_noexpand_fyi 98 | #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ 99 | #endif 100 | #ifndef uthash_expand_fyi 101 | #define uthash_expand_fyi(tbl) /* can be defined to log expands */ 102 | #endif 103 | 104 | /* initial number of buckets */ 105 | #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ 106 | #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ 107 | #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ 108 | 109 | /* calculate the element whose hash handle address is hhp */ 110 | #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) 111 | /* calculate the hash handle from element address elp */ 112 | #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho))) 113 | 114 | #define HASH_VALUE(keyptr,keylen,hashv) \ 115 | do { \ 116 | HASH_FCN(keyptr, keylen, hashv); \ 117 | } while (0) 118 | 119 | #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ 120 | do { \ 121 | (out) = NULL; \ 122 | if (head) { \ 123 | unsigned _hf_bkt; \ 124 | HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ 125 | if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ 126 | HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ 127 | } \ 128 | } \ 129 | } while (0) 130 | 131 | #define HASH_FIND(hh,head,keyptr,keylen,out) \ 132 | do { \ 133 | unsigned _hf_hashv; \ 134 | HASH_VALUE(keyptr, keylen, _hf_hashv); \ 135 | HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ 136 | } while (0) 137 | 138 | #ifdef HASH_BLOOM 139 | #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) 140 | #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) 141 | #define HASH_BLOOM_MAKE(tbl) \ 142 | do { \ 143 | (tbl)->bloom_nbits = HASH_BLOOM; \ 144 | (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ 145 | if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ 146 | memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ 147 | (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ 148 | } while (0) 149 | 150 | #define HASH_BLOOM_FREE(tbl) \ 151 | do { \ 152 | uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ 153 | } while (0) 154 | 155 | #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) 156 | #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) 157 | 158 | #define HASH_BLOOM_ADD(tbl,hashv) \ 159 | HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) 160 | 161 | #define HASH_BLOOM_TEST(tbl,hashv) \ 162 | HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U))) 163 | 164 | #else 165 | #define HASH_BLOOM_MAKE(tbl) 166 | #define HASH_BLOOM_FREE(tbl) 167 | #define HASH_BLOOM_ADD(tbl,hashv) 168 | #define HASH_BLOOM_TEST(tbl,hashv) (1) 169 | #define HASH_BLOOM_BYTELEN 0U 170 | #endif 171 | 172 | #define HASH_MAKE_TABLE(hh,head) \ 173 | do { \ 174 | (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ 175 | sizeof(UT_hash_table)); \ 176 | if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ 177 | memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ 178 | (head)->hh.tbl->tail = &((head)->hh); \ 179 | (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ 180 | (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ 181 | (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ 182 | (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ 183 | HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ 184 | if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ 185 | memset((head)->hh.tbl->buckets, 0, \ 186 | HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ 187 | HASH_BLOOM_MAKE((head)->hh.tbl); \ 188 | (head)->hh.tbl->signature = HASH_SIGNATURE; \ 189 | } while (0) 190 | 191 | #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ 192 | do { \ 193 | (replaced) = NULL; \ 194 | HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ 195 | if (replaced) { \ 196 | HASH_DELETE(hh, head, replaced); \ 197 | } \ 198 | HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ 199 | } while (0) 200 | 201 | #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ 202 | do { \ 203 | (replaced) = NULL; \ 204 | HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ 205 | if (replaced) { \ 206 | HASH_DELETE(hh, head, replaced); \ 207 | } \ 208 | HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ 209 | } while (0) 210 | 211 | #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ 212 | do { \ 213 | unsigned _hr_hashv; \ 214 | HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ 215 | HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ 216 | } while (0) 217 | 218 | #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ 219 | do { \ 220 | unsigned _hr_hashv; \ 221 | HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ 222 | HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ 223 | } while (0) 224 | 225 | #define HASH_APPEND_LIST(hh, head, add) \ 226 | do { \ 227 | (add)->hh.next = NULL; \ 228 | (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ 229 | (head)->hh.tbl->tail->next = (add); \ 230 | (head)->hh.tbl->tail = &((add)->hh); \ 231 | } while (0) 232 | 233 | #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ 234 | do { \ 235 | unsigned _ha_bkt; \ 236 | (add)->hh.hashv = (hashval); \ 237 | (add)->hh.key = (char*) (keyptr); \ 238 | (add)->hh.keylen = (unsigned) (keylen_in); \ 239 | if (!(head)) { \ 240 | (add)->hh.next = NULL; \ 241 | (add)->hh.prev = NULL; \ 242 | (head) = (add); \ 243 | HASH_MAKE_TABLE(hh, head); \ 244 | } else { \ 245 | void *_hs_iter = (head); \ 246 | (add)->hh.tbl = (head)->hh.tbl; \ 247 | do { \ 248 | if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) \ 249 | break; \ 250 | } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ 251 | if (_hs_iter) { \ 252 | (add)->hh.next = _hs_iter; \ 253 | if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ 254 | HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ 255 | } else { \ 256 | (head) = (add); \ 257 | } \ 258 | HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ 259 | } else { \ 260 | HASH_APPEND_LIST(hh, head, add); \ 261 | } \ 262 | } \ 263 | (head)->hh.tbl->num_items++; \ 264 | HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ 265 | HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh); \ 266 | HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ 267 | HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ 268 | HASH_FSCK(hh, head); \ 269 | } while (0) 270 | 271 | #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ 272 | do { \ 273 | unsigned _hs_hashv; \ 274 | HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ 275 | HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ 276 | } while (0) 277 | 278 | #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ 279 | HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) 280 | 281 | #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ 282 | HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) 283 | 284 | #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ 285 | do { \ 286 | unsigned _ha_bkt; \ 287 | (add)->hh.hashv = (hashval); \ 288 | (add)->hh.key = (char*) (keyptr); \ 289 | (add)->hh.keylen = (unsigned) (keylen_in); \ 290 | if (!(head)) { \ 291 | (add)->hh.next = NULL; \ 292 | (add)->hh.prev = NULL; \ 293 | (head) = (add); \ 294 | HASH_MAKE_TABLE(hh, head); \ 295 | } else { \ 296 | (add)->hh.tbl = (head)->hh.tbl; \ 297 | HASH_APPEND_LIST(hh, head, add); \ 298 | } \ 299 | (head)->hh.tbl->num_items++; \ 300 | HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ 301 | HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh); \ 302 | HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ 303 | HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ 304 | HASH_FSCK(hh, head); \ 305 | } while (0) 306 | 307 | #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ 308 | do { \ 309 | unsigned _ha_hashv; \ 310 | HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ 311 | HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ 312 | } while (0) 313 | 314 | #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ 315 | HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) 316 | 317 | #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ 318 | HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) 319 | 320 | #define HASH_TO_BKT(hashv,num_bkts,bkt) \ 321 | do { \ 322 | bkt = ((hashv) & ((num_bkts) - 1U)); \ 323 | } while (0) 324 | 325 | /* delete "delptr" from the hash table. 326 | * "the usual" patch-up process for the app-order doubly-linked-list. 327 | * The use of _hd_hh_del below deserves special explanation. 328 | * These used to be expressed using (delptr) but that led to a bug 329 | * if someone used the same symbol for the head and deletee, like 330 | * HASH_DELETE(hh,users,users); 331 | * We want that to work, but by changing the head (users) below 332 | * we were forfeiting our ability to further refer to the deletee (users) 333 | * in the patch-up process. Solution: use scratch space to 334 | * copy the deletee pointer, then the latter references are via that 335 | * scratch pointer rather than through the repointed (users) symbol. 336 | */ 337 | #define HASH_DELETE(hh,head,delptr) \ 338 | do { \ 339 | struct UT_hash_handle *_hd_hh_del; \ 340 | if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ 341 | uthash_free((head)->hh.tbl->buckets, \ 342 | (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ 343 | HASH_BLOOM_FREE((head)->hh.tbl); \ 344 | uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ 345 | head = NULL; \ 346 | } else { \ 347 | unsigned _hd_bkt; \ 348 | _hd_hh_del = &((delptr)->hh); \ 349 | if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ 350 | (head)->hh.tbl->tail = \ 351 | (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ 352 | (head)->hh.tbl->hho); \ 353 | } \ 354 | if ((delptr)->hh.prev != NULL) { \ 355 | ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ 356 | (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ 357 | } else { \ 358 | DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ 359 | } \ 360 | if (_hd_hh_del->next != NULL) { \ 361 | ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ 362 | (head)->hh.tbl->hho))->prev = \ 363 | _hd_hh_del->prev; \ 364 | } \ 365 | HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ 366 | HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ 367 | (head)->hh.tbl->num_items--; \ 368 | } \ 369 | HASH_FSCK(hh,head); \ 370 | } while (0) 371 | 372 | 373 | /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ 374 | #define HASH_FIND_STR(head,findstr,out) \ 375 | HASH_FIND(hh,head,findstr,(unsigned)uthash_strlen(findstr),out) 376 | #define HASH_ADD_STR(head,strfield,add) \ 377 | HASH_ADD(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add) 378 | #define HASH_REPLACE_STR(head,strfield,add,replaced) \ 379 | HASH_REPLACE(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add,replaced) 380 | #define HASH_FIND_INT(head,findint,out) \ 381 | HASH_FIND(hh,head,findint,sizeof(int),out) 382 | #define HASH_ADD_INT(head,intfield,add) \ 383 | HASH_ADD(hh,head,intfield,sizeof(int),add) 384 | #define HASH_REPLACE_INT(head,intfield,add,replaced) \ 385 | HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) 386 | #define HASH_FIND_PTR(head,findptr,out) \ 387 | HASH_FIND(hh,head,findptr,sizeof(void *),out) 388 | #define HASH_ADD_PTR(head,ptrfield,add) \ 389 | HASH_ADD(hh,head,ptrfield,sizeof(void *),add) 390 | #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ 391 | HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) 392 | #define HASH_DEL(head,delptr) \ 393 | HASH_DELETE(hh,head,delptr) 394 | 395 | /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. 396 | * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. 397 | */ 398 | #ifdef HASH_DEBUG 399 | #define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) 400 | #define HASH_FSCK(hh,head) \ 401 | do { \ 402 | struct UT_hash_handle *_thh; \ 403 | if (head) { \ 404 | unsigned _bkt_i; \ 405 | unsigned _count; \ 406 | char *_prev; \ 407 | _count = 0; \ 408 | for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ 409 | unsigned _bkt_count = 0; \ 410 | _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ 411 | _prev = NULL; \ 412 | while (_thh) { \ 413 | if (_prev != (char*)(_thh->hh_prev)) { \ 414 | HASH_OOPS("invalid hh_prev %p, actual %p\n", \ 415 | _thh->hh_prev, _prev ); \ 416 | } \ 417 | _bkt_count++; \ 418 | _prev = (char*)(_thh); \ 419 | _thh = _thh->hh_next; \ 420 | } \ 421 | _count += _bkt_count; \ 422 | if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ 423 | HASH_OOPS("invalid bucket count %u, actual %u\n", \ 424 | (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ 425 | } \ 426 | } \ 427 | if (_count != (head)->hh.tbl->num_items) { \ 428 | HASH_OOPS("invalid hh item count %u, actual %u\n", \ 429 | (head)->hh.tbl->num_items, _count ); \ 430 | } \ 431 | /* traverse hh in app order; check next/prev integrity, count */ \ 432 | _count = 0; \ 433 | _prev = NULL; \ 434 | _thh = &(head)->hh; \ 435 | while (_thh) { \ 436 | _count++; \ 437 | if (_prev !=(char*)(_thh->prev)) { \ 438 | HASH_OOPS("invalid prev %p, actual %p\n", \ 439 | _thh->prev, _prev ); \ 440 | } \ 441 | _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ 442 | _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ 443 | (head)->hh.tbl->hho) : NULL ); \ 444 | } \ 445 | if (_count != (head)->hh.tbl->num_items) { \ 446 | HASH_OOPS("invalid app item count %u, actual %u\n", \ 447 | (head)->hh.tbl->num_items, _count ); \ 448 | } \ 449 | } \ 450 | } while (0) 451 | #else 452 | #define HASH_FSCK(hh,head) 453 | #endif 454 | 455 | /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to 456 | * the descriptor to which this macro is defined for tuning the hash function. 457 | * The app can #include to get the prototype for write(2). */ 458 | #ifdef HASH_EMIT_KEYS 459 | #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ 460 | do { \ 461 | unsigned _klen = fieldlen; \ 462 | write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ 463 | write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ 464 | } while (0) 465 | #else 466 | #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) 467 | #endif 468 | 469 | /* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ 470 | #ifdef HASH_FUNCTION 471 | #define HASH_FCN HASH_FUNCTION 472 | #else 473 | #define HASH_FCN HASH_JEN 474 | #endif 475 | 476 | /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ 477 | #define HASH_BER(key,keylen,hashv) \ 478 | do { \ 479 | unsigned _hb_keylen=(unsigned)keylen; \ 480 | const unsigned char *_hb_key=(const unsigned char*)(key); \ 481 | (hashv) = 0; \ 482 | while (_hb_keylen-- != 0U) { \ 483 | (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ 484 | } \ 485 | } while (0) 486 | 487 | 488 | /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at 489 | * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ 490 | #define HASH_SAX(key,keylen,hashv) \ 491 | do { \ 492 | unsigned _sx_i; \ 493 | const unsigned char *_hs_key=(const unsigned char*)(key); \ 494 | hashv = 0; \ 495 | for(_sx_i=0; _sx_i < keylen; _sx_i++) { \ 496 | hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ 497 | } \ 498 | } while (0) 499 | /* FNV-1a variation */ 500 | #define HASH_FNV(key,keylen,hashv) \ 501 | do { \ 502 | unsigned _fn_i; \ 503 | const unsigned char *_hf_key=(const unsigned char*)(key); \ 504 | hashv = 2166136261U; \ 505 | for(_fn_i=0; _fn_i < keylen; _fn_i++) { \ 506 | hashv = hashv ^ _hf_key[_fn_i]; \ 507 | hashv = hashv * 16777619U; \ 508 | } \ 509 | } while (0) 510 | 511 | #define HASH_OAT(key,keylen,hashv) \ 512 | do { \ 513 | unsigned _ho_i; \ 514 | const unsigned char *_ho_key=(const unsigned char*)(key); \ 515 | hashv = 0; \ 516 | for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ 517 | hashv += _ho_key[_ho_i]; \ 518 | hashv += (hashv << 10); \ 519 | hashv ^= (hashv >> 6); \ 520 | } \ 521 | hashv += (hashv << 3); \ 522 | hashv ^= (hashv >> 11); \ 523 | hashv += (hashv << 15); \ 524 | } while (0) 525 | 526 | #define HASH_JEN_MIX(a,b,c) \ 527 | do { \ 528 | a -= b; a -= c; a ^= ( c >> 13 ); \ 529 | b -= c; b -= a; b ^= ( a << 8 ); \ 530 | c -= a; c -= b; c ^= ( b >> 13 ); \ 531 | a -= b; a -= c; a ^= ( c >> 12 ); \ 532 | b -= c; b -= a; b ^= ( a << 16 ); \ 533 | c -= a; c -= b; c ^= ( b >> 5 ); \ 534 | a -= b; a -= c; a ^= ( c >> 3 ); \ 535 | b -= c; b -= a; b ^= ( a << 10 ); \ 536 | c -= a; c -= b; c ^= ( b >> 15 ); \ 537 | } while (0) 538 | 539 | #define HASH_JEN(key,keylen,hashv) \ 540 | do { \ 541 | unsigned _hj_i,_hj_j,_hj_k; \ 542 | unsigned const char *_hj_key=(unsigned const char*)(key); \ 543 | hashv = 0xfeedbeefu; \ 544 | _hj_i = _hj_j = 0x9e3779b9u; \ 545 | _hj_k = (unsigned)(keylen); \ 546 | while (_hj_k >= 12U) { \ 547 | _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ 548 | + ( (unsigned)_hj_key[2] << 16 ) \ 549 | + ( (unsigned)_hj_key[3] << 24 ) ); \ 550 | _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ 551 | + ( (unsigned)_hj_key[6] << 16 ) \ 552 | + ( (unsigned)_hj_key[7] << 24 ) ); \ 553 | hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ 554 | + ( (unsigned)_hj_key[10] << 16 ) \ 555 | + ( (unsigned)_hj_key[11] << 24 ) ); \ 556 | \ 557 | HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ 558 | \ 559 | _hj_key += 12; \ 560 | _hj_k -= 12U; \ 561 | } \ 562 | hashv += (unsigned)(keylen); \ 563 | switch ( _hj_k ) { \ 564 | case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ 565 | case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ 566 | case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ 567 | case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ 568 | case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ 569 | case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ 570 | case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ 571 | case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ 572 | case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ 573 | case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ 574 | case 1: _hj_i += _hj_key[0]; \ 575 | } \ 576 | HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ 577 | } while (0) 578 | 579 | /* The Paul Hsieh hash function */ 580 | #undef get16bits 581 | #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ 582 | || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) 583 | #define get16bits(d) (*((const uint16_t *) (d))) 584 | #endif 585 | 586 | #if !defined (get16bits) 587 | #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ 588 | +(uint32_t)(((const uint8_t *)(d))[0]) ) 589 | #endif 590 | #define HASH_SFH(key,keylen,hashv) \ 591 | do { \ 592 | unsigned const char *_sfh_key=(unsigned const char*)(key); \ 593 | uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ 594 | \ 595 | unsigned _sfh_rem = _sfh_len & 3U; \ 596 | _sfh_len >>= 2; \ 597 | hashv = 0xcafebabeu; \ 598 | \ 599 | /* Main loop */ \ 600 | for (;_sfh_len > 0U; _sfh_len--) { \ 601 | hashv += get16bits (_sfh_key); \ 602 | _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ 603 | hashv = (hashv << 16) ^ _sfh_tmp; \ 604 | _sfh_key += 2U*sizeof (uint16_t); \ 605 | hashv += hashv >> 11; \ 606 | } \ 607 | \ 608 | /* Handle end cases */ \ 609 | switch (_sfh_rem) { \ 610 | case 3: hashv += get16bits (_sfh_key); \ 611 | hashv ^= hashv << 16; \ 612 | hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ 613 | hashv += hashv >> 11; \ 614 | break; \ 615 | case 2: hashv += get16bits (_sfh_key); \ 616 | hashv ^= hashv << 11; \ 617 | hashv += hashv >> 17; \ 618 | break; \ 619 | case 1: hashv += *_sfh_key; \ 620 | hashv ^= hashv << 10; \ 621 | hashv += hashv >> 1; \ 622 | } \ 623 | \ 624 | /* Force "avalanching" of final 127 bits */ \ 625 | hashv ^= hashv << 3; \ 626 | hashv += hashv >> 5; \ 627 | hashv ^= hashv << 4; \ 628 | hashv += hashv >> 17; \ 629 | hashv ^= hashv << 25; \ 630 | hashv += hashv >> 6; \ 631 | } while (0) 632 | 633 | #ifdef HASH_USING_NO_STRICT_ALIASING 634 | /* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. 635 | * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. 636 | * MurmurHash uses the faster approach only on CPU's where we know it's safe. 637 | * 638 | * Note the preprocessor built-in defines can be emitted using: 639 | * 640 | * gcc -m64 -dM -E - < /dev/null (on gcc) 641 | * cc -## a.c (where a.c is a simple test file) (Sun Studio) 642 | */ 643 | #if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) 644 | #define MUR_GETBLOCK(p,i) p[i] 645 | #else /* non intel */ 646 | #define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL) 647 | #define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL) 648 | #define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL) 649 | #define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL) 650 | #define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) 651 | #if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) 652 | #define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) 653 | #define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) 654 | #define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) 655 | #else /* assume little endian non-intel */ 656 | #define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) 657 | #define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) 658 | #define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) 659 | #endif 660 | #define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ 661 | (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ 662 | (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ 663 | MUR_ONE_THREE(p)))) 664 | #endif 665 | #define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) 666 | #define MUR_FMIX(_h) \ 667 | do { \ 668 | _h ^= _h >> 16; \ 669 | _h *= 0x85ebca6bu; \ 670 | _h ^= _h >> 13; \ 671 | _h *= 0xc2b2ae35u; \ 672 | _h ^= _h >> 16; \ 673 | } while (0) 674 | 675 | #define HASH_MUR(key,keylen,hashv) \ 676 | do { \ 677 | const uint8_t *_mur_data = (const uint8_t*)(key); \ 678 | const int _mur_nblocks = (int)(keylen) / 4; \ 679 | uint32_t _mur_h1 = 0xf88D5353u; \ 680 | uint32_t _mur_c1 = 0xcc9e2d51u; \ 681 | uint32_t _mur_c2 = 0x1b873593u; \ 682 | uint32_t _mur_k1 = 0; \ 683 | const uint8_t *_mur_tail; \ 684 | const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \ 685 | int _mur_i; \ 686 | for(_mur_i = -_mur_nblocks; _mur_i!=0; _mur_i++) { \ 687 | _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ 688 | _mur_k1 *= _mur_c1; \ 689 | _mur_k1 = MUR_ROTL32(_mur_k1,15); \ 690 | _mur_k1 *= _mur_c2; \ 691 | \ 692 | _mur_h1 ^= _mur_k1; \ 693 | _mur_h1 = MUR_ROTL32(_mur_h1,13); \ 694 | _mur_h1 = (_mur_h1*5U) + 0xe6546b64u; \ 695 | } \ 696 | _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4)); \ 697 | _mur_k1=0; \ 698 | switch((keylen) & 3U) { \ 699 | case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \ 700 | case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8; /* FALLTHROUGH */ \ 701 | case 1: _mur_k1 ^= (uint32_t)_mur_tail[0]; \ 702 | _mur_k1 *= _mur_c1; \ 703 | _mur_k1 = MUR_ROTL32(_mur_k1,15); \ 704 | _mur_k1 *= _mur_c2; \ 705 | _mur_h1 ^= _mur_k1; \ 706 | } \ 707 | _mur_h1 ^= (uint32_t)(keylen); \ 708 | MUR_FMIX(_mur_h1); \ 709 | hashv = _mur_h1; \ 710 | } while (0) 711 | #endif /* HASH_USING_NO_STRICT_ALIASING */ 712 | 713 | /* iterate over items in a known bucket to find desired item */ 714 | #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ 715 | do { \ 716 | if ((head).hh_head != NULL) { \ 717 | DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ 718 | } else { \ 719 | (out) = NULL; \ 720 | } \ 721 | while ((out) != NULL) { \ 722 | if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ 723 | if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) { \ 724 | break; \ 725 | } \ 726 | } \ 727 | if ((out)->hh.hh_next != NULL) { \ 728 | DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ 729 | } else { \ 730 | (out) = NULL; \ 731 | } \ 732 | } \ 733 | } while (0) 734 | 735 | /* add an item to a bucket */ 736 | #define HASH_ADD_TO_BKT(head,addhh) \ 737 | do { \ 738 | head.count++; \ 739 | (addhh)->hh_next = head.hh_head; \ 740 | (addhh)->hh_prev = NULL; \ 741 | if (head.hh_head != NULL) { (head).hh_head->hh_prev = (addhh); } \ 742 | (head).hh_head=addhh; \ 743 | if ((head.count >= ((head.expand_mult+1U) * HASH_BKT_CAPACITY_THRESH)) \ 744 | && ((addhh)->tbl->noexpand != 1U)) { \ 745 | HASH_EXPAND_BUCKETS((addhh)->tbl); \ 746 | } \ 747 | } while (0) 748 | 749 | /* remove an item from a given bucket */ 750 | #define HASH_DEL_IN_BKT(hh,head,hh_del) \ 751 | (head).count--; \ 752 | if ((head).hh_head == hh_del) { \ 753 | (head).hh_head = hh_del->hh_next; \ 754 | } \ 755 | if (hh_del->hh_prev) { \ 756 | hh_del->hh_prev->hh_next = hh_del->hh_next; \ 757 | } \ 758 | if (hh_del->hh_next) { \ 759 | hh_del->hh_next->hh_prev = hh_del->hh_prev; \ 760 | } 761 | 762 | /* Bucket expansion has the effect of doubling the number of buckets 763 | * and redistributing the items into the new buckets. Ideally the 764 | * items will distribute more or less evenly into the new buckets 765 | * (the extent to which this is true is a measure of the quality of 766 | * the hash function as it applies to the key domain). 767 | * 768 | * With the items distributed into more buckets, the chain length 769 | * (item count) in each bucket is reduced. Thus by expanding buckets 770 | * the hash keeps a bound on the chain length. This bounded chain 771 | * length is the essence of how a hash provides constant time lookup. 772 | * 773 | * The calculation of tbl->ideal_chain_maxlen below deserves some 774 | * explanation. First, keep in mind that we're calculating the ideal 775 | * maximum chain length based on the *new* (doubled) bucket count. 776 | * In fractions this is just n/b (n=number of items,b=new num buckets). 777 | * Since the ideal chain length is an integer, we want to calculate 778 | * ceil(n/b). We don't depend on floating point arithmetic in this 779 | * hash, so to calculate ceil(n/b) with integers we could write 780 | * 781 | * ceil(n/b) = (n/b) + ((n%b)?1:0) 782 | * 783 | * and in fact a previous version of this hash did just that. 784 | * But now we have improved things a bit by recognizing that b is 785 | * always a power of two. We keep its base 2 log handy (call it lb), 786 | * so now we can write this with a bit shift and logical AND: 787 | * 788 | * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) 789 | * 790 | */ 791 | #define HASH_EXPAND_BUCKETS(tbl) \ 792 | do { \ 793 | unsigned _he_bkt; \ 794 | unsigned _he_bkt_i; \ 795 | struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ 796 | UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ 797 | _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ 798 | 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ 799 | if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ 800 | memset(_he_new_buckets, 0, \ 801 | 2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ 802 | tbl->ideal_chain_maxlen = \ 803 | (tbl->num_items >> (tbl->log2_num_buckets+1U)) + \ 804 | (((tbl->num_items & ((tbl->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ 805 | tbl->nonideal_items = 0; \ 806 | for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ 807 | { \ 808 | _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ 809 | while (_he_thh != NULL) { \ 810 | _he_hh_nxt = _he_thh->hh_next; \ 811 | HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2U, _he_bkt); \ 812 | _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ 813 | if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ 814 | tbl->nonideal_items++; \ 815 | _he_newbkt->expand_mult = _he_newbkt->count / \ 816 | tbl->ideal_chain_maxlen; \ 817 | } \ 818 | _he_thh->hh_prev = NULL; \ 819 | _he_thh->hh_next = _he_newbkt->hh_head; \ 820 | if (_he_newbkt->hh_head != NULL) { _he_newbkt->hh_head->hh_prev = \ 821 | _he_thh; } \ 822 | _he_newbkt->hh_head = _he_thh; \ 823 | _he_thh = _he_hh_nxt; \ 824 | } \ 825 | } \ 826 | uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ 827 | tbl->num_buckets *= 2U; \ 828 | tbl->log2_num_buckets++; \ 829 | tbl->buckets = _he_new_buckets; \ 830 | tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ 831 | (tbl->ineff_expands+1U) : 0U; \ 832 | if (tbl->ineff_expands > 1U) { \ 833 | tbl->noexpand=1; \ 834 | uthash_noexpand_fyi(tbl); \ 835 | } \ 836 | uthash_expand_fyi(tbl); \ 837 | } while (0) 838 | 839 | 840 | /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ 841 | /* Note that HASH_SORT assumes the hash handle name to be hh. 842 | * HASH_SRT was added to allow the hash handle name to be passed in. */ 843 | #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) 844 | #define HASH_SRT(hh,head,cmpfcn) \ 845 | do { \ 846 | unsigned _hs_i; \ 847 | unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ 848 | struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ 849 | if (head != NULL) { \ 850 | _hs_insize = 1; \ 851 | _hs_looping = 1; \ 852 | _hs_list = &((head)->hh); \ 853 | while (_hs_looping != 0U) { \ 854 | _hs_p = _hs_list; \ 855 | _hs_list = NULL; \ 856 | _hs_tail = NULL; \ 857 | _hs_nmerges = 0; \ 858 | while (_hs_p != NULL) { \ 859 | _hs_nmerges++; \ 860 | _hs_q = _hs_p; \ 861 | _hs_psize = 0; \ 862 | for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ 863 | _hs_psize++; \ 864 | _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ 865 | ((void*)((char*)(_hs_q->next) + \ 866 | (head)->hh.tbl->hho)) : NULL); \ 867 | if (! (_hs_q) ) { break; } \ 868 | } \ 869 | _hs_qsize = _hs_insize; \ 870 | while ((_hs_psize > 0U) || ((_hs_qsize > 0U) && (_hs_q != NULL))) {\ 871 | if (_hs_psize == 0U) { \ 872 | _hs_e = _hs_q; \ 873 | _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ 874 | ((void*)((char*)(_hs_q->next) + \ 875 | (head)->hh.tbl->hho)) : NULL); \ 876 | _hs_qsize--; \ 877 | } else if ( (_hs_qsize == 0U) || (_hs_q == NULL) ) { \ 878 | _hs_e = _hs_p; \ 879 | if (_hs_p != NULL){ \ 880 | _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ 881 | ((void*)((char*)(_hs_p->next) + \ 882 | (head)->hh.tbl->hho)) : NULL); \ 883 | } \ 884 | _hs_psize--; \ 885 | } else if (( \ 886 | cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ 887 | DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ 888 | ) <= 0) { \ 889 | _hs_e = _hs_p; \ 890 | if (_hs_p != NULL){ \ 891 | _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ? \ 892 | ((void*)((char*)(_hs_p->next) + \ 893 | (head)->hh.tbl->hho)) : NULL); \ 894 | } \ 895 | _hs_psize--; \ 896 | } else { \ 897 | _hs_e = _hs_q; \ 898 | _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ? \ 899 | ((void*)((char*)(_hs_q->next) + \ 900 | (head)->hh.tbl->hho)) : NULL); \ 901 | _hs_qsize--; \ 902 | } \ 903 | if ( _hs_tail != NULL ) { \ 904 | _hs_tail->next = ((_hs_e != NULL) ? \ 905 | ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ 906 | } else { \ 907 | _hs_list = _hs_e; \ 908 | } \ 909 | if (_hs_e != NULL) { \ 910 | _hs_e->prev = ((_hs_tail != NULL) ? \ 911 | ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ 912 | } \ 913 | _hs_tail = _hs_e; \ 914 | } \ 915 | _hs_p = _hs_q; \ 916 | } \ 917 | if (_hs_tail != NULL){ \ 918 | _hs_tail->next = NULL; \ 919 | } \ 920 | if ( _hs_nmerges <= 1U ) { \ 921 | _hs_looping=0; \ 922 | (head)->hh.tbl->tail = _hs_tail; \ 923 | DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ 924 | } \ 925 | _hs_insize *= 2U; \ 926 | } \ 927 | HASH_FSCK(hh,head); \ 928 | } \ 929 | } while (0) 930 | 931 | /* This function selects items from one hash into another hash. 932 | * The end result is that the selected items have dual presence 933 | * in both hashes. There is no copy of the items made; rather 934 | * they are added into the new hash through a secondary hash 935 | * hash handle that must be present in the structure. */ 936 | #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ 937 | do { \ 938 | unsigned _src_bkt, _dst_bkt; \ 939 | void *_last_elt=NULL, *_elt; \ 940 | UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ 941 | ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ 942 | if (src != NULL) { \ 943 | for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ 944 | for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ 945 | _src_hh != NULL; \ 946 | _src_hh = _src_hh->hh_next) { \ 947 | _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ 948 | if (cond(_elt)) { \ 949 | _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ 950 | _dst_hh->key = _src_hh->key; \ 951 | _dst_hh->keylen = _src_hh->keylen; \ 952 | _dst_hh->hashv = _src_hh->hashv; \ 953 | _dst_hh->prev = _last_elt; \ 954 | _dst_hh->next = NULL; \ 955 | if (_last_elt_hh != NULL) { _last_elt_hh->next = _elt; } \ 956 | if (dst == NULL) { \ 957 | DECLTYPE_ASSIGN(dst,_elt); \ 958 | HASH_MAKE_TABLE(hh_dst,dst); \ 959 | } else { \ 960 | _dst_hh->tbl = (dst)->hh_dst.tbl; \ 961 | } \ 962 | HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ 963 | HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ 964 | (dst)->hh_dst.tbl->num_items++; \ 965 | _last_elt = _elt; \ 966 | _last_elt_hh = _dst_hh; \ 967 | } \ 968 | } \ 969 | } \ 970 | } \ 971 | HASH_FSCK(hh_dst,dst); \ 972 | } while (0) 973 | 974 | #define HASH_CLEAR(hh,head) \ 975 | do { \ 976 | if (head != NULL) { \ 977 | uthash_free((head)->hh.tbl->buckets, \ 978 | (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ 979 | HASH_BLOOM_FREE((head)->hh.tbl); \ 980 | uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ 981 | (head)=NULL; \ 982 | } \ 983 | } while (0) 984 | 985 | #define HASH_OVERHEAD(hh,head) \ 986 | ((head != NULL) ? ( \ 987 | (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ 988 | ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ 989 | sizeof(UT_hash_table) + \ 990 | (HASH_BLOOM_BYTELEN))) : 0U) 991 | 992 | #ifdef NO_DECLTYPE 993 | #define HASH_ITER(hh,head,el,tmp) \ 994 | for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ 995 | (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) 996 | #else 997 | #define HASH_ITER(hh,head,el,tmp) \ 998 | for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ 999 | (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) 1000 | #endif 1001 | 1002 | /* obtain a count of items in the hash */ 1003 | #define HASH_COUNT(head) HASH_CNT(hh,head) 1004 | #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) 1005 | 1006 | typedef struct UT_hash_bucket { 1007 | struct UT_hash_handle *hh_head; 1008 | unsigned count; 1009 | 1010 | /* expand_mult is normally set to 0. In this situation, the max chain length 1011 | * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If 1012 | * the bucket's chain exceeds this length, bucket expansion is triggered). 1013 | * However, setting expand_mult to a non-zero value delays bucket expansion 1014 | * (that would be triggered by additions to this particular bucket) 1015 | * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. 1016 | * (The multiplier is simply expand_mult+1). The whole idea of this 1017 | * multiplier is to reduce bucket expansions, since they are expensive, in 1018 | * situations where we know that a particular bucket tends to be overused. 1019 | * It is better to let its chain length grow to a longer yet-still-bounded 1020 | * value, than to do an O(n) bucket expansion too often. 1021 | */ 1022 | unsigned expand_mult; 1023 | 1024 | } UT_hash_bucket; 1025 | 1026 | /* random signature used only to find hash tables in external analysis */ 1027 | #define HASH_SIGNATURE 0xa0111fe1u 1028 | #define HASH_BLOOM_SIGNATURE 0xb12220f2u 1029 | 1030 | typedef struct UT_hash_table { 1031 | UT_hash_bucket *buckets; 1032 | unsigned num_buckets, log2_num_buckets; 1033 | unsigned num_items; 1034 | struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ 1035 | ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ 1036 | 1037 | /* in an ideal situation (all buckets used equally), no bucket would have 1038 | * more than ceil(#items/#buckets) items. that's the ideal chain length. */ 1039 | unsigned ideal_chain_maxlen; 1040 | 1041 | /* nonideal_items is the number of items in the hash whose chain position 1042 | * exceeds the ideal chain maxlen. these items pay the penalty for an uneven 1043 | * hash distribution; reaching them in a chain traversal takes >ideal steps */ 1044 | unsigned nonideal_items; 1045 | 1046 | /* ineffective expands occur when a bucket doubling was performed, but 1047 | * afterward, more than half the items in the hash had nonideal chain 1048 | * positions. If this happens on two consecutive expansions we inhibit any 1049 | * further expansion, as it's not helping; this happens when the hash 1050 | * function isn't a good fit for the key domain. When expansion is inhibited 1051 | * the hash will still work, albeit no longer in constant time. */ 1052 | unsigned ineff_expands, noexpand; 1053 | 1054 | uint32_t signature; /* used only to find hash tables in external analysis */ 1055 | #ifdef HASH_BLOOM 1056 | uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ 1057 | uint8_t *bloom_bv; 1058 | uint8_t bloom_nbits; 1059 | #endif 1060 | 1061 | } UT_hash_table; 1062 | 1063 | typedef struct UT_hash_handle { 1064 | struct UT_hash_table *tbl; 1065 | void *prev; /* prev element in app order */ 1066 | void *next; /* next element in app order */ 1067 | struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ 1068 | struct UT_hash_handle *hh_next; /* next hh in bucket order */ 1069 | void *key; /* ptr to enclosing struct's key */ 1070 | unsigned keylen; /* enclosing struct's key len */ 1071 | unsigned hashv; /* result of hash-fcn(key) */ 1072 | } UT_hash_handle; 1073 | 1074 | #endif /* UTHASH_H */ 1075 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.59c) 2 | AC_PACKAGE_VERSION(1.0.0) 3 | AC_PACKAGE_NAME(mqtree) 4 | AC_INIT(mqtree, 1.0.0, [], mqtree) 5 | 6 | # Checks for programs. 7 | AC_PROG_CC 8 | AC_PROG_MAKE_SET 9 | 10 | if test "x$GCC" = "xyes"; then 11 | CFLAGS="$CFLAGS -Wall" 12 | fi 13 | 14 | # Checks for typedefs, structures, and compiler characteristics. 15 | AC_C_CONST 16 | 17 | # Checks for library functions. 18 | AC_FUNC_MALLOC 19 | AC_HEADER_STDC 20 | 21 | # Checks Erlang runtime and compiler 22 | AC_ERLANG_NEED_ERL 23 | AC_ERLANG_NEED_ERLC 24 | 25 | # Checks and sets ERLANG_ROOT_DIR and ERLANG_LIB_DIR variable 26 | # AC_ERLANG_SUBST_ROOT_DIR 27 | # AC_ERLANG_SUBST_LIB_DIR 28 | 29 | AC_ARG_ENABLE(gcov, 30 | [AC_HELP_STRING([--enable-gcov], [compile with gcov enabled (default: no)])], 31 | [case "${enableval}" in 32 | yes) gcov=true ;; 33 | no) gcov=false ;; 34 | *) AC_MSG_ERROR(bad value ${enableval} for --enable-gcov) ;; 35 | esac],[gcov=false]) 36 | 37 | AC_SUBST(gcov) 38 | 39 | AC_CONFIG_FILES([vars.config]) 40 | 41 | AC_OUTPUT 42 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/processone/mqtree/5baaafaf3b65b63b746b1587044a1792bbafb560/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Evgeny Khramtsov 3 | %%% @copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved. 4 | %%% 5 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %%% you may not use this file except in compliance with the License. 7 | %%% You may obtain a copy of the License at 8 | %%% 9 | %%% http://www.apache.org/licenses/LICENSE-2.0 10 | %%% 11 | %%% Unless required by applicable law or agreed to in writing, software 12 | %%% distributed under the License is distributed on an "AS IS" BASIS, 13 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %%% See the License for the specific language governing permissions and 15 | %%% limitations under the License. 16 | %%% 17 | %%%------------------------------------------------------------------- 18 | {erl_opts, [debug_info]}. 19 | 20 | {port_env, [{"CFLAGS", "$CFLAGS -std=c99 -g -O2 -Wall"}, 21 | {"ERL_LDFLAGS", " -L$ERL_EI_LIBDIR -lei"}, 22 | {"LDFLAGS", "$LDFLAGS -lpthread"}]}. 23 | 24 | {port_specs, [{"priv/lib/mqtree.so", ["c_src/mqtree.c"]}]}. 25 | 26 | {deps, [{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.27"}}}]}. 27 | 28 | {clean_files, ["c_src/mqtree.gcda", "c_src/mqtree.gcno"]}. 29 | 30 | {cover_enabled, true}. 31 | {cover_export_enabled, true}. 32 | {coveralls_coverdata , "_build/test/cover/eunit.coverdata"}. 33 | {coveralls_service_name , "github"}. 34 | 35 | {xref_checks, [undefined_function_calls, undefined_functions, 36 | deprecated_function_calls, deprecated_functions]}. 37 | 38 | %% Local Variables: 39 | %% mode: erlang 40 | %% End: 41 | %% vim: set filetype=erlang tabstop=8: 42 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | %%%---------------------------------------------------------------------- 2 | %%% File : rebar.config 3 | %%% Author : Mickael Remond 4 | %%% Purpose : Rebar build script. Compliant with rebar and rebar3. 5 | %%% Created : 15 Dec 2015 by Mickael Remond 6 | %%% 7 | %%% Copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved. 8 | %%% 9 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 10 | %%% you may not use this file except in compliance with the License. 11 | %%% You may obtain a copy of the License at 12 | %%% 13 | %%% http://www.apache.org/licenses/LICENSE-2.0 14 | %%% 15 | %%% Unless required by applicable law or agreed to in writing, software 16 | %%% distributed under the License is distributed on an "AS IS" BASIS, 17 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | %%% See the License for the specific language governing permissions and 19 | %%% limitations under the License. 20 | %%% 21 | %%%---------------------------------------------------------------------- 22 | Cfg = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])) of 23 | {ok, Terms} -> 24 | Terms; 25 | _Err -> 26 | [] 27 | end ++ [{cflags, "-g -O2 -Wall"}, {ldflags, ""}, {with_gcov, "false"}], 28 | {cflags, CfgCFlags} = lists:keyfind(cflags, 1, Cfg), 29 | {ldflags, CfgLDFlags} = lists:keyfind(ldflags, 1, Cfg), 30 | {with_gcov, CfgWithGCov} = lists:keyfind(with_gcov, 1, Cfg), 31 | 32 | SysVersion = lists:map(fun erlang:list_to_integer/1, 33 | string:tokens(erlang:system_info(version), ".")), 34 | 35 | IsRebar3 = case application:get_key(rebar, vsn) of 36 | {ok, VSN} -> 37 | [VSN1 | _] = string:tokens(VSN, "-"), 38 | [Maj|_] = string:tokens(VSN1, "."), 39 | (list_to_integer(Maj) >= 3); 40 | undefined -> 41 | lists:keymember(mix, 1, application:loaded_applications()) 42 | end, 43 | 44 | ModCfg0 = fun(F, Cfg, [Key|Tail], Op, Default) -> 45 | {OldVal,PartCfg} = case lists:keytake(Key, 1, Cfg) of 46 | {value, {_, V1}, V2} -> {V1, V2}; 47 | false -> {if Tail == [] -> Default; true -> [] end, Cfg} 48 | end, 49 | case Tail of 50 | [] -> 51 | [{Key, Op(OldVal)} | PartCfg]; 52 | _ -> 53 | [{Key, F(F, OldVal, Tail, Op, Default)} | PartCfg] 54 | end 55 | end, 56 | ModCfg = fun(Cfg, Keys, Op, Default) -> ModCfg0(ModCfg0, Cfg, Keys, Op, 57 | Default) end, 58 | 59 | ModCfgS = fun(Cfg, Keys, Val) -> ModCfg0(ModCfg0, Cfg, Keys, fun(_V) -> 60 | Val end, "") end, 61 | 62 | 63 | FilterConfig = fun(F, Cfg, [{Path, true, ModFun, Default} | Tail]) -> 64 | F(F, ModCfg0(ModCfg0, Cfg, Path, ModFun, Default), Tail); 65 | (F, Cfg, [_ | Tail]) -> 66 | F(F, Cfg, Tail); 67 | (F, Cfg, []) -> 68 | Cfg 69 | end, 70 | 71 | AppendStr = fun(Append) -> 72 | fun("") -> 73 | Append; 74 | (Val) -> 75 | Val ++ " " ++ Append 76 | end 77 | end, 78 | AppendList = fun(Append) -> 79 | fun(Val) -> 80 | Val ++ Append 81 | end 82 | end, 83 | 84 | Rebar3DepsFilter = fun(DepsList) -> 85 | lists:map(fun({DepName,_, {git,_, {tag,Version}}}) -> 86 | {DepName, Version}; 87 | (Dep) -> 88 | Dep 89 | end, DepsList) 90 | end, 91 | 92 | GlobalDepsFilter = fun(Deps) -> 93 | DepNames = lists:map(fun({DepName, _, _}) -> DepName; 94 | ({DepName, _}) -> DepName 95 | end, Deps), 96 | lists:filtermap(fun(Dep) -> 97 | case code:lib_dir(Dep) of 98 | {error, _} -> 99 | {true,"Unable to locate dep '"++atom_to_list(Dep)++"' in system deps."}; 100 | _ -> 101 | false 102 | end 103 | end, DepNames) 104 | end, 105 | 106 | GithubConfig = case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of 107 | {"true", Token} when is_list(Token) -> 108 | CONFIG1 = [{coveralls_repo_token, Token}, 109 | {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")}, 110 | {coveralls_commit_sha, os:getenv("GITHUB_SHA")}, 111 | {coveralls_service_number, os:getenv("GITHUB_RUN_NUMBER")}], 112 | case os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" 113 | andalso string:tokens(os:getenv("GITHUB_REF"), "/") of 114 | [_, "pull", PRNO, _] -> 115 | [{coveralls_service_pull_request, PRNO} | CONFIG1]; 116 | _ -> 117 | CONFIG1 118 | end; 119 | _ -> 120 | [] 121 | end, 122 | 123 | Rules = [ 124 | {[port_env, "CFLAGS"], SysVersion >= [7, 3], 125 | AppendStr("-DHAS_ERTS_EXIT"), "$CFLAGS"}, 126 | {[port_env, "CFLAGS"], true, 127 | AppendStr(CfgCFlags), "$CFLAGS"}, 128 | {[port_env, "LDFLAGS"], true, 129 | AppendStr(CfgLDFlags), "$LDFLAGS"}, 130 | {[post_hooks], (not IsRebar3) and (CfgWithGCov == "true"), 131 | AppendList([{eunit, "gcov -o c_src mqtree"}, 132 | {eunit, "mv *.gcov .eunit/"}]), []}, 133 | {[post_hooks], IsRebar3 and (CfgWithGCov == "true"), 134 | AppendList([{eunit, "gcov -o c_src mqtree"}, 135 | {eunit, "mv *.gcov _build/test/cover/"}]), []}, 136 | {[port_env, "LDFLAGS"], CfgWithGCov == "true", 137 | AppendStr("--coverage"), ""}, 138 | {[port_env, "CFLAGS"], CfgWithGCov == "true", 139 | AppendStr("--coverage"), ""}, 140 | {[deps], IsRebar3, 141 | Rebar3DepsFilter, []}, 142 | {[plugins], IsRebar3, 143 | AppendList([pc]), []}, 144 | {[provider_hooks], IsRebar3, 145 | AppendList([{pre, [ 146 | {compile, {pc, compile}}, 147 | {clean, {pc, clean}} 148 | ]}]), []}, 149 | {[plugins], os:getenv("COVERALLS") == "true", 150 | AppendList([{coveralls, {git, 151 | "https://github.com/processone/coveralls-erl.git", 152 | {branch, "addjsonfile"}}} ]), []}, 153 | {[deps], os:getenv("USE_GLOBAL_DEPS") /= false, 154 | GlobalDepsFilter, []} 155 | ], 156 | 157 | 158 | Config = FilterConfig(FilterConfig, CONFIG, Rules) ++ GithubConfig, 159 | 160 | %io:format("Rules:~n~p~n~nCONFIG:~n~p~n~nConfig:~n~p~n", [Rules, CONFIG, Config]), 161 | 162 | Config. 163 | 164 | %% Local Variables: 165 | %% mode: erlang 166 | %% End: 167 | %% vim: set filetype=erlang tabstop=8: 168 | -------------------------------------------------------------------------------- /src/mqtree.app.src: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Evgeny Khramtsov 3 | %%% @copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved. 4 | %%% 5 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %%% you may not use this file except in compliance with the License. 7 | %%% You may obtain a copy of the License at 8 | %%% 9 | %%% http://www.apache.org/licenses/LICENSE-2.0 10 | %%% 11 | %%% Unless required by applicable law or agreed to in writing, software 12 | %%% distributed under the License is distributed on an "AS IS" BASIS, 13 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %%% See the License for the specific language governing permissions and 15 | %%% limitations under the License. 16 | %%% 17 | %%%------------------------------------------------------------------- 18 | {application, mqtree, 19 | [ 20 | {description, "Index tree for MQTT topic filters"}, 21 | {vsn, "1.0.18"}, 22 | {registered, []}, 23 | {applications, [kernel, stdlib, p1_utils]}, 24 | {env, []}, 25 | 26 | %% hex.pm packaging: 27 | {files, ["src/", "c_src/*.c", "c_src/*.h", "rebar.config", 28 | "rebar.config.script", "README.md", "LICENSE"]}, 29 | {licenses, ["Apache 2.0"]}, 30 | {links, [{"Github", "https://github.com/processone/mqtree"}]} 31 | ]}. 32 | 33 | %% Local Variables: 34 | %% mode: erlang 35 | %% End: 36 | %% vim: set filetype=erlang tabstop=8: 37 | -------------------------------------------------------------------------------- /src/mqtree.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Evgeny Khramtsov 3 | %%% @copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved. 4 | %%% 5 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %%% you may not use this file except in compliance with the License. 7 | %%% You may obtain a copy of the License at 8 | %%% 9 | %%% http://www.apache.org/licenses/LICENSE-2.0 10 | %%% 11 | %%% Unless required by applicable law or agreed to in writing, software 12 | %%% distributed under the License is distributed on an "AS IS" BASIS, 13 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %%% See the License for the specific language governing permissions and 15 | %%% limitations under the License. 16 | %%% 17 | %%%------------------------------------------------------------------- 18 | -module(mqtree). 19 | -on_load(load_nif/0). 20 | 21 | %% API 22 | -export([new/0, insert/2, delete/2, match/2, refc/2, 23 | clear/1, size/1, is_empty/1]). 24 | -export([register/2, unregister/1, whereis/1, registered/0]). 25 | %% For debugging 26 | -export([dump/1, to_list/1]). 27 | 28 | -type path() :: iodata(). 29 | -opaque tree() :: reference(). 30 | -export_type([tree/0, path/0]). 31 | 32 | %%%=================================================================== 33 | %%% API 34 | %%%=================================================================== 35 | -spec new() -> tree(). 36 | new() -> 37 | erlang:nif_error({nif_not_loaded, ?MODULE}). 38 | 39 | -spec insert(tree(), path()) -> ok. 40 | insert(_Tree, _Path) -> 41 | erlang:nif_error({nif_not_loaded, ?MODULE}). 42 | 43 | -spec delete(tree(), path()) -> ok. 44 | delete(_Tree, _Path) -> 45 | erlang:nif_error({nif_not_loaded, ?MODULE}). 46 | 47 | -spec match(tree(), path()) -> [binary()]. 48 | match(_Tree, _Path) -> 49 | erlang:nif_error({nif_not_loaded, ?MODULE}). 50 | 51 | -spec refc(tree(), path()) -> non_neg_integer(). 52 | refc(_Tree, _Path) -> 53 | erlang:nif_error({nif_not_loaded, ?MODULE}). 54 | 55 | -spec clear(tree()) -> ok. 56 | clear(_Tree) -> 57 | erlang:nif_error({nif_not_loaded, ?MODULE}). 58 | 59 | -spec size(tree()) -> non_neg_integer(). 60 | size(_Tree) -> 61 | erlang:nif_error({nif_not_loaded, ?MODULE}). 62 | 63 | -spec is_empty(tree()) -> boolean(). 64 | is_empty(_Tree) -> 65 | erlang:nif_error({nif_not_loaded, ?MODULE}). 66 | 67 | -spec register(atom(), tree()) -> ok. 68 | register(_Name, _Tree) -> 69 | erlang:nif_error({nif_not_loaded, ?MODULE}). 70 | 71 | -spec unregister(atom()) -> ok. 72 | unregister(_Name) -> 73 | erlang:nif_error({nif_not_loaded, ?MODULE}). 74 | 75 | -spec whereis(atom()) -> tree() | undefined. 76 | whereis(_Name) -> 77 | erlang:nif_error({nif_not_loaded, ?MODULE}). 78 | 79 | -spec registered() -> [atom()]. 80 | registered() -> 81 | erlang:nif_error({nif_not_loaded, ?MODULE}). 82 | 83 | %%%=================================================================== 84 | %%% For testing/debugging 85 | %%%=================================================================== 86 | -type tree_node() :: {string(), string() | none, 87 | non_neg_integer(), [tree_node()]}. 88 | 89 | -spec dump(tree()) -> [tree_node()]. 90 | dump(_Tree) -> 91 | erlang:nif_error({nif_not_loaded, ?MODULE}). 92 | 93 | -spec to_list(tree()) -> [{binary(), non_neg_integer()}]. 94 | to_list(_Tree) -> 95 | erlang:nif_error({nif_not_loaded, ?MODULE}). 96 | 97 | %%%=================================================================== 98 | %%% Internal functions 99 | %%%=================================================================== 100 | load_nif() -> 101 | case os:getenv("COVERALLS") of 102 | "true" -> ok; 103 | _ -> load_nif2() 104 | end. 105 | load_nif2() -> 106 | Path = p1_nif_utils:get_so_path(?MODULE, [?MODULE], atom_to_list(?MODULE)), 107 | case erlang:load_nif(Path, 0) of 108 | ok -> ok; 109 | {error, {upgrade, _}} -> ok; 110 | {error, {Reason, Text}} -> 111 | error_logger:error_msg("Failed to load NIF ~s: ~s (~p)", 112 | [Path, Text, Reason]), 113 | erlang:nif_error(Reason) 114 | end. 115 | -------------------------------------------------------------------------------- /test/mqtree_test.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Evgeny Khramtsov 3 | %%% @copyright (C) 2002-2025 ProcessOne, SARL. All Rights Reserved. 4 | %%% 5 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 6 | %%% you may not use this file except in compliance with the License. 7 | %%% You may obtain a copy of the License at 8 | %%% 9 | %%% http://www.apache.org/licenses/LICENSE-2.0 10 | %%% 11 | %%% Unless required by applicable law or agreed to in writing, software 12 | %%% distributed under the License is distributed on an "AS IS" BASIS, 13 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | %%% See the License for the specific language governing permissions and 15 | %%% limitations under the License. 16 | %%% 17 | %%%------------------------------------------------------------------- 18 | -module(mqtree_test). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | 21 | -define(assertTree(L), 22 | case L of 23 | [] -> 24 | ?assertEqual([], mqtree:dump(T)), 25 | ?assertEqual([], mqtree:to_list(T)); 26 | _ -> 27 | ?assertEqual(L, lists:sort(mqtree:to_list(T))) 28 | end). 29 | 30 | -define(assertInsert(E), 31 | ?assertEqual(ok, mqtree:insert(T, E))). 32 | 33 | -define(assertDelete(E), 34 | ?assertEqual(ok, mqtree:delete(T, E))). 35 | 36 | %%%=================================================================== 37 | %%% Tests 38 | %%%=================================================================== 39 | new_test() -> 40 | T = mqtree:new(), 41 | ?assertTree([]). 42 | 43 | insert_test() -> 44 | T = mqtree:new(), 45 | Path = <<"/a/b/c">>, 46 | ?assertInsert(Path), 47 | ?assertTree([{Path, 1}]). 48 | 49 | is_empty_test() -> 50 | T = mqtree:new(), 51 | ?assert(mqtree:is_empty(T)), 52 | ?assertInsert(<<"/">>), 53 | ?assert(not mqtree:is_empty(T)). 54 | 55 | insert_then_delete_test() -> 56 | T = mqtree:new(), 57 | Path = <<"a/b">>, 58 | ?assertInsert(Path), 59 | ?assertDelete(Path), 60 | ?assertTree([]). 61 | 62 | insert_empty_then_delete_empty_test() -> 63 | T = mqtree:new(), 64 | ?assertInsert(<<>>), 65 | ?assertTree([]), 66 | ?assertDelete(<<>>), 67 | ?assertTree([]). 68 | 69 | insert_then_delete_empty_test() -> 70 | T = mqtree:new(), 71 | Path = <<"/a/b">>, 72 | ?assertInsert(Path), 73 | ?assertTree([{Path, 1}]), 74 | ?assertDelete(<<>>), 75 | ?assertTree([{Path, 1}]). 76 | 77 | insert_then_delete_shuffle_test_() -> 78 | {timeout, 60, fun insert_then_delete_shuffle/0}. 79 | 80 | insert_then_delete_shuffle() -> 81 | T = mqtree:new(), 82 | Check = lists:sort(rand_paths()), 83 | lists:foldl( 84 | fun(insert, Refc) -> 85 | lists:foreach( 86 | fun(Path) -> ?assertInsert(Path) end, 87 | rand_paths()), 88 | Refc1 = Refc+1, 89 | ?assertTree([{P, Refc1} || P <- Check]), 90 | Refc1; 91 | (delete, Refc) -> 92 | lists:foreach( 93 | fun(Path) -> ?assertDelete(Path) end, 94 | rand_paths()), 95 | Refc1 = Refc-1, 96 | case Refc1 of 97 | 0 -> 98 | ?assertTree([]); 99 | _ -> 100 | ?assertTree([{P, Refc1} || P <- Check]) 101 | end, 102 | Refc1 103 | end, 0, rand_funs()). 104 | 105 | refc_test() -> 106 | T = mqtree:new(), 107 | lists:foreach( 108 | fun(Refc) -> 109 | lists:foreach( 110 | fun(P) -> 111 | ?assertEqual(Refc, mqtree:refc(T, P)), 112 | ?assertInsert(P) 113 | end, rand_paths()) 114 | end, lists:seq(0, 5)), 115 | lists:foreach( 116 | fun(Refc) -> 117 | lists:foreach( 118 | fun(P) -> 119 | ?assertDelete(P), 120 | ?assertEqual(Refc, mqtree:refc(T, P)) 121 | end, rand_paths()) 122 | end, lists:seq(5, 0, -1)). 123 | 124 | clear_test() -> 125 | T = mqtree:new(), 126 | lists:foreach( 127 | fun(_) -> 128 | lists:foreach(fun(P) -> ?assertInsert(P) end, rand_paths()), 129 | ?assertEqual(ok, mqtree:clear(T)), 130 | ?assertTree([]) 131 | end, lists:seq(1, 10)). 132 | 133 | clear_empty_test() -> 134 | T = mqtree:new(), 135 | ?assertEqual(ok, mqtree:clear(T)), 136 | ?assertTree([]). 137 | 138 | size_test() -> 139 | T = mqtree:new(), 140 | ?assertEqual(0, mqtree:size(T)), 141 | Paths = rand_paths(), 142 | lists:foreach( 143 | fun(_) -> 144 | lists:foreach(fun(P) -> ?assertInsert(P) end, rand_paths()), 145 | ?assert(mqtree:size(T) == length(Paths)) 146 | end, [1,2,3]), 147 | ?assertEqual(ok, mqtree:clear(T)), 148 | ?assertEqual(0, mqtree:size(T)). 149 | 150 | delete_non_existent_test() -> 151 | T = mqtree:new(), 152 | lists:foreach( 153 | fun(_) -> 154 | lists:foreach(fun(P) -> ?assertDelete(P) end, rand_paths()), 155 | ?assertTree([]) 156 | end, lists:seq(1, 10)). 157 | 158 | insert_then_delete_non_existent_test() -> 159 | T = mqtree:new(), 160 | Inserts = rand_paths("@$%&*"), 161 | Check = [{P, 1} || P <- lists:sort(Inserts)], 162 | lists:foreach(fun(P) -> ?assertInsert(P) end, Inserts), 163 | lists:foreach( 164 | fun(_) -> 165 | lists:foreach(fun(P) -> ?assertDelete(P) end, rand_paths()), 166 | ?assertTree(Check) 167 | end, lists:seq(1, 10)). 168 | 169 | match_all_test() -> 170 | T = mqtree:new(), 171 | lists:foreach( 172 | fun(_) -> 173 | ?assertInsert("#"), 174 | lists:foreach( 175 | fun(P) -> 176 | ?assertEqual([<<"#">>], mqtree:match(T, P)) 177 | end, rand_paths()) 178 | end, lists:seq(1, 10)). 179 | 180 | match_none_test() -> 181 | T = mqtree:new(), 182 | lists:foreach( 183 | fun(P) -> 184 | ?assertEqual([], mqtree:match(T, P)) 185 | end, rand_paths()). 186 | 187 | match_exact_test() -> 188 | T = mqtree:new(), 189 | lists:foreach(fun(P) -> ?assertInsert(P) end, rand_paths()), 190 | lists:foreach( 191 | fun(P) -> 192 | ?assertEqual([P], mqtree:match(T, P)) 193 | end, rand_paths()). 194 | 195 | match_tail_test() -> 196 | T = mqtree:new(), 197 | Filter = <<"a/b/#">>, 198 | ?assertInsert(Filter), 199 | ?assertEqual([], mqtree:match(T, "a/bc")), 200 | ?assertEqual([Filter], mqtree:match(T, "a/b")), 201 | ?assertEqual([Filter], mqtree:match(T, "a/b/")), 202 | ?assertEqual([Filter], mqtree:match(T, "a/b/c")), 203 | ?assertEqual([Filter], mqtree:match(T, "a/b/c/d")). 204 | 205 | match_plus_test() -> 206 | T = mqtree:new(), 207 | Filter = lists:sort([<> || A<-"+a", B<-"+b"]), 208 | lists:foreach(fun(P) -> ?assertInsert(P) end, Filter), 209 | ?assertEqual([<<"+/+">>], mqtree:match(T, "/")), 210 | ?assertEqual([<<"+/+">>], mqtree:match(T, "x/")), 211 | ?assertEqual([<<"+/+">>], mqtree:match(T, "/y")), 212 | ?assertEqual([<<"+/+">>], mqtree:match(T, "x/y")), 213 | ?assertEqual([<<"+/+">>, <<"a/+">>], mqtree:match(T, "a/")), 214 | ?assertEqual([<<"+/+">>, <<"a/+">>], mqtree:match(T, "a/y")), 215 | ?assertEqual([<<"+/+">>, <<"+/b">>], mqtree:match(T, "/b")), 216 | ?assertEqual([<<"+/+">>, <<"+/b">>], mqtree:match(T, "x/b")), 217 | ?assertEqual(Filter, lists:sort(mqtree:match(T, "a/b"))). 218 | 219 | 'match_begins_with_$_test'() -> 220 | T = mqtree:new(), 221 | Filters = ["#", "+", "+/", "+/+"], 222 | Topics = [<<"$SYS">>, <<"$SYS/some">>, <<"$">>, <<"$/some">>], 223 | lists:foreach(fun(P) -> ?assertInsert(P) end, Filters ++ Topics), 224 | lists:foreach(fun(P) -> ?assertEqual([P], mqtree:match(T, P)) end, Topics). 225 | 226 | whereis_non_existent_test() -> 227 | ?assertEqual(undefined, mqtree:whereis(test_tree)). 228 | 229 | unregister_non_existent_test() -> 230 | ?assertError(badarg, mqtree:unregister(test_tree)). 231 | 232 | register_test() -> 233 | T = mqtree:new(), 234 | ?assertEqual(ok, mqtree:register(test_tree, T)), 235 | ?assertEqual(T, mqtree:whereis(test_tree)), 236 | ?assertEqual(ok, mqtree:unregister(test_tree)). 237 | 238 | double_register_same_tree_test() -> 239 | T = mqtree:new(), 240 | ?assertEqual(ok, mqtree:register(test_tree, T)), 241 | ?assertError(badarg, mqtree:register(test_tree, T)), 242 | ?assertEqual(ok, mqtree:unregister(test_tree)). 243 | 244 | double_register_another_tree_test() -> 245 | T1 = mqtree:new(), 246 | T2 = mqtree:new(), 247 | ?assertEqual(ok, mqtree:register(test_tree, T1)), 248 | ?assertError(badarg, mqtree:register(test_tree, T2)), 249 | ?assertEqual(ok, mqtree:unregister(test_tree)). 250 | 251 | unregister_test() -> 252 | T = mqtree:new(), 253 | ?assertEqual(ok, mqtree:register(test_tree, T)), 254 | ?assertEqual(ok, mqtree:unregister(test_tree)), 255 | ?assertEqual(undefined, mqtree:whereis(test_tree)). 256 | 257 | double_unregister_test() -> 258 | T = mqtree:new(), 259 | ?assertEqual(ok, mqtree:register(test_tree, T)), 260 | ?assertEqual(ok, mqtree:unregister(test_tree)), 261 | ?assertError(badarg, mqtree:unregister(test_tree)). 262 | 263 | rename_test() -> 264 | T = mqtree:new(), 265 | ?assertEqual(ok, mqtree:register(test_tree_1, T)), 266 | ?assertEqual(ok, mqtree:register(test_tree_2, T)), 267 | ?assertEqual(undefined, mqtree:whereis(test_tree_1)), 268 | ?assertError(badarg, mqtree:unregister(test_tree_1)), 269 | ?assertEqual(T, mqtree:whereis(test_tree_2)), 270 | ?assertEqual(ok, mqtree:unregister(test_tree_2)). 271 | 272 | register_undefined_test() -> 273 | T = mqtree:new(), 274 | ?assertError(badarg, mqtree:register(undefined, T)). 275 | 276 | registered_test() -> 277 | Names = [list_to_atom("test_tree_" ++ integer_to_list(I)) 278 | || I <- lists:seq(1, 9)], 279 | lists:foldl( 280 | fun(Name, Acc) -> 281 | ?assertEqual(Acc, lists:sort(mqtree:registered())), 282 | T = mqtree:new(), 283 | ?assertEqual(ok, mqtree:register(Name, T)), 284 | [Name|Acc] 285 | end, [], lists:reverse(Names)), 286 | lists:foldl( 287 | fun(_, [Name|Acc]) -> 288 | ?assertEqual(ok, mqtree:unregister(Name)), 289 | ?assertEqual(Acc, lists:sort(mqtree:registered())), 290 | Acc 291 | end, Names, Names). 292 | 293 | %%%=================================================================== 294 | %%% Internal functions 295 | %%%=================================================================== 296 | rand_paths() -> 297 | rand_paths("/abcd"). 298 | 299 | rand_paths(Set) -> 300 | L1 = [{p1_rand:uniform(), <>} || A<-Set], 301 | L2 = [{p1_rand:uniform(), <>} || A<-Set, B<-Set], 302 | L3 = [{p1_rand:uniform(), <>} || A<-Set, B<-Set, C<-Set], 303 | L4 = [{p1_rand:uniform(), <>} || A<-Set, B<-Set, C<-Set, D<-Set], 304 | L5 = [{p1_rand:uniform(), <>} || A<-Set, B<-Set, C<-Set, D<-Set, E<-Set], 305 | [Path || {_, Path} <- lists:keysort(1, L1++L2++L3++L4++L5)]. 306 | 307 | rand_funs() -> 308 | lists:flatmap( 309 | fun(_) -> 310 | I = p1_rand:uniform(5), 311 | Inserts = lists:duplicate(I, insert), 312 | Deletes = lists:duplicate(I, delete), 313 | Inserts ++ Deletes 314 | end, [1,2,3,4,5]). 315 | -------------------------------------------------------------------------------- /vars.config.in: -------------------------------------------------------------------------------- 1 | {with_gcov, "@gcov@"}. 2 | 3 | %% Local Variables: 4 | %% mode: erlang 5 | %% End: 6 | %% vim: set filetype=erlang tabstop=8: 7 | --------------------------------------------------------------------------------