├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── test-erlang-otp-21.3.yaml │ └── test-erlang-otp-22.3.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-MPL-RabbitMQ ├── Makefile ├── README.md ├── elvis.config ├── erlang.mk ├── rabbitmq-components.mk ├── src ├── amqp10_client.erl ├── amqp10_client.hrl ├── amqp10_client_app.erl ├── amqp10_client_connection.erl ├── amqp10_client_connection_sup.erl ├── amqp10_client_connections_sup.erl ├── amqp10_client_frame_reader.erl ├── amqp10_client_session.erl ├── amqp10_client_sessions_sup.erl ├── amqp10_client_sup.erl ├── amqp10_client_types.erl └── amqp10_msg.erl └── test ├── activemq_ct_helpers.erl ├── mock_server.erl ├── msg_SUITE.erl ├── system_SUITE.erl └── system_SUITE_data └── conf ├── activemq.xml └── activemq_no_anon.xml /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for using RabbitMQ. 2 | 3 | **STOP NOW AND READ THIS** BEFORE OPENING A NEW ISSUE ON GITHUB 4 | 5 | Unless you are CERTAIN you have found a reproducible problem in RabbitMQ or 6 | have a **specific, actionable** suggestion for our team, you must first ask 7 | your question or discuss your suspected issue on the mailing list: 8 | 9 | https://groups.google.com/forum/#!forum/rabbitmq-users 10 | 11 | Team RabbitMQ does not use GitHub issues for discussions, investigations, root 12 | cause analysis and so on. 13 | 14 | Please take the time to read the CONTRIBUTING.md document for instructions on 15 | how to effectively ask a question or report a suspected issue: 16 | 17 | https://github.com/rabbitmq/rabbitmq-server/blob/master/CONTRIBUTING.md#github-issues 18 | 19 | Following these rules **will save time** for both you and RabbitMQ's maintainers. 20 | 21 | Thank you. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | Please describe the big picture of your changes here to communicate to the 4 | RabbitMQ team why we should accept this pull request. If it fixes a bug or 5 | resolves a feature request, be sure to link to that issue. 6 | 7 | A pull request that doesn't explain **why** the change was made has a much 8 | lower chance of being accepted. 9 | 10 | If English isn't your first language, don't worry about it and try to 11 | communicate the problem you are trying to solve to the best of your abilities. 12 | As long as we can understand the intent, it's all good. 13 | 14 | ## Types of Changes 15 | 16 | What types of changes does your code introduce to this project? 17 | _Put an `x` in the boxes that apply_ 18 | 19 | - [ ] Bug fix (non-breaking change which fixes issue #NNNN) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause an observable behavior change in existing systems) 22 | - [ ] Documentation improvements (corrections, new content, etc) 23 | - [ ] Cosmetic change (whitespace, formatting, etc) 24 | 25 | ## Checklist 26 | 27 | _Put an `x` in the boxes that apply. You can also fill these out after creating 28 | the PR. If you're unsure about any of them, don't hesitate to ask on the 29 | mailing list. We're here to help! This is simply a reminder of what we are 30 | going to look for before merging your code._ 31 | 32 | - [ ] I have read the `CONTRIBUTING.md` document 33 | - [ ] I have signed the CA (see https://cla.pivotal.io/sign/rabbitmq) 34 | - [ ] All tests pass locally with my changes 35 | - [ ] I have added tests that prove my fix is effective or that my feature works 36 | - [ ] I have added necessary documentation (if appropriate) 37 | - [ ] Any dependent changes have been merged and published in related repositories 38 | 39 | ## Further Comments 40 | 41 | If this is a relatively large or complex change, kick off the discussion by 42 | explaining why you chose the solution you did and what alternatives you 43 | considered, etc. 44 | -------------------------------------------------------------------------------- /.github/workflows/test-erlang-otp-21.3.yaml: -------------------------------------------------------------------------------- 1 | # vim:sw=2:et: 2 | # https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow 3 | name: "Test - Erlang 21.3" 4 | on: 5 | push: 6 | repository_dispatch: 7 | types: 8 | - new-commit-to-dep-release-branch 9 | jobs: 10 | # vim:sw=2:et: 11 | checks: 12 | name: checks 13 | runs-on: ubuntu-18.04 14 | steps: 15 | - name: CHECKOUT REPOSITORY 16 | uses: actions/checkout@v2 17 | # https://github.com/marketplace/actions/setup-elixir 18 | - name: CONFIGURE OTP & ELIXIR 19 | uses: actions/setup-elixir@v1 20 | with: 21 | otp-version: 21.3 22 | # https://github.com/elixir-lang/elixir/releases 23 | elixir-version: 1.10.4 24 | - name: CHECK RABBITMQ COMPONENTS 25 | # https://github.community/t5/GitHub-Actions/How-can-I-set-an-expression-as-an-environment-variable-at/m-p/41804/highlight/true#M4751 26 | id: ref 27 | run: | 28 | branch_or_tag_name=${GITHUB_REF#refs/*/} 29 | echo "::set-output name=branch_or_tag_name::$branch_or_tag_name" 30 | make check-rabbitmq-components.mk base_rmq_ref=master current_rmq_ref=$branch_or_tag_name 31 | # https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows 32 | - name: CACHE DEPS 33 | uses: actions/cache@v1 34 | with: 35 | path: deps 36 | key: otp-21.3_git-${{ github.sha }}_deps 37 | - name: RESOLVE & COMPILE DEPS 38 | run: | 39 | make deps test-deps base_rmq_ref=master current_rmq_ref=${{ steps.ref.outputs.branch_or_tag_name }} 40 | echo "Capture versions of the RabbitMQ components used in this workflow..." 41 | make amqp10_client-rabbitmq-deps.mk 42 | mv amqp10_client-rabbitmq-deps.mk deps/ 43 | echo "Remove directories not used in the subsequent jobs..." 44 | rm -fr deps/*/{.git,test} 45 | - name: UPLOAD DEPS VERSIONS 46 | uses: actions/upload-artifact@v2-preview 47 | with: 48 | name: amqp10_client-rabbitmq-deps.mk 49 | path: deps/amqp10_client-rabbitmq-deps.mk 50 | - name: CHECK CROSS REFERENCES 51 | run: | 52 | make xref base_rmq_ref=master current_rmq_ref=${{ steps.ref.outputs.branch_or_tag_name }} 53 | - name: COMPILE FOR TEST 54 | run: | 55 | make test-build base_rmq_ref=master current_rmq_ref=${{ steps.ref.outputs.branch_or_tag_name }} 56 | - name: CACHE SECONDARY UMBRELLAS 57 | if: success() && 'oldest' == 'oldest' 58 | uses: actions/cache@v1 59 | with: 60 | path: umbrellas 61 | key: secondary-umbrellas-v3.7.26-v3.8.3-erlang-21.3-rev3 62 | - name: PREPARE SECONDARY UMBRELLA COPIES 63 | if: success() && 'oldest' == 'oldest' 64 | run: | 65 | # ---------------------------------------------------------- 66 | # CAUTION: 67 | # The same script must be copied to `03-CT_SUITE.yaml`. It is used to 68 | # recreate the umbrellas if the cache restore fails. 69 | # ---------------------------------------------------------- 70 | set -x 71 | for version in v3.7.26 v3.8.3; do 72 | umbrella="umbrellas/$version" 73 | if ! test -d "$umbrella" || 74 | ! make -C "$umbrella/deps/amqp10_client" test-dist; then 75 | rm -rf "$umbrella" 76 | 77 | # Fetch the master Umbrella; the final umbrellas are created from 78 | # the master copy. 79 | if ! test -d umbrellas/master; then 80 | git config --global advice.detachedHead false 81 | git clone \ 82 | https://github.com/rabbitmq/rabbitmq-public-umbrella.git \ 83 | umbrellas/master 84 | make -C umbrellas/master co # To get RabbitMQ components. 85 | fi 86 | 87 | # We copy the master Umbrella and checkout the appropriate tag. 88 | cp -a umbrellas/master "$umbrella" 89 | git -C "$umbrella" checkout "master" 90 | make -C "$umbrella" up BRANCH="$version" 91 | # To remove third-party deps which were checked out when the 92 | # projects were on the `master` branch. Thus, possibly not the 93 | # version pinning we expect. We update the Umbrella one last time 94 | # to fetch the correct third-party deps. 95 | make -C "$umbrella" clean-3rd-party-repos 96 | make -C "$umbrella" up 97 | make -C "$umbrella/deps/amqp10_client" test-dist 98 | rm -fr "$umbrella"/deps/*/{.git,test} "$umbrella"/.git 99 | fi 100 | done 101 | rm -fr umbrellas/master 102 | # vim:sw=2:et: 103 | dialyzer: 104 | name: dialyzer 105 | needs: [checks] 106 | runs-on: ubuntu-18.04 107 | steps: 108 | - name: CHECKOUT REPOSITORY 109 | if: success() && 'oldest' == 'latest' 110 | uses: actions/checkout@v2 111 | # https://github.com/marketplace/actions/setup-elixir 112 | - name: CONFIGURE OTP & ELIXIR 113 | if: success() && 'oldest' == 'latest' 114 | uses: actions/setup-elixir@v1 115 | with: 116 | otp-version: 21.3 117 | # https://github.com/elixir-lang/elixir/releases 118 | elixir-version: 1.10.4 119 | - name: CACHE DEPS 120 | if: success() && 'oldest' == 'latest' 121 | uses: actions/cache@v1 122 | with: 123 | path: deps 124 | key: otp-21.3_git-${{ github.sha }}_deps 125 | - name: CACHE DIALYZER PLT 126 | if: success() && 'oldest' == 'latest' 127 | uses: actions/cache@v1 128 | with: 129 | path: .amqp10_client.plt 130 | key: plt-amqp10_client-erlang-21.3-g${{ github.sha }} 131 | - name: RUN DIALYZER 132 | if: success() && 'oldest' == 'latest' 133 | run: | 134 | branch_or_tag_name=${GITHUB_REF#refs/*/} 135 | make dialyze \ 136 | base_rmq_ref=master \ 137 | current_rmq_ref=$branch_or_tag_name \ 138 | FULL= 139 | # vim:sw=2:et: 140 | eunit: 141 | needs: [checks] 142 | # https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts 143 | name: eunit 144 | runs-on: ubuntu-18.04 145 | steps: 146 | - name: CHECKOUT REPOSITORY 147 | uses: actions/checkout@v2 148 | # https://github.com/marketplace/actions/setup-elixir 149 | - name: CONFIGURE OTP & ELIXIR 150 | uses: actions/setup-elixir@v1 151 | with: 152 | otp-version: 21.3 153 | # https://github.com/elixir-lang/elixir/releases 154 | elixir-version: 1.10.4 155 | - name: CACHE DEPS 156 | uses: actions/cache@v1 157 | with: 158 | path: deps 159 | key: otp-21.3_git-${{ github.sha }}_deps 160 | - name: RUN TESTS 161 | run: | 162 | ! test -d ebin || touch ebin/* 163 | branch_or_tag_name=${GITHUB_REF#refs/*/} 164 | make eunit \ 165 | base_rmq_ref=master \ 166 | current_rmq_ref=$branch_or_tag_name \ 167 | FULL= \ 168 | FAIL_FAST=1 \ 169 | SKIP_AS_ERROR=1 170 | # vim:sw=2:et: 171 | ct-msg: 172 | needs: [checks] 173 | # https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts 174 | name: ct-msg 175 | runs-on: ubuntu-18.04 176 | steps: 177 | - name: CHECKOUT REPOSITORY 178 | uses: actions/checkout@v2 179 | # https://github.com/marketplace/actions/setup-elixir 180 | - name: CONFIGURE OTP & ELIXIR 181 | uses: actions/setup-elixir@v1 182 | with: 183 | otp-version: 21.3 184 | # https://github.com/elixir-lang/elixir/releases 185 | elixir-version: 1.10.4 186 | - name: CACHE DEPS 187 | uses: actions/cache@v1 188 | with: 189 | path: deps 190 | key: otp-21.3_git-${{ github.sha }}_deps 191 | - name: RUN TESTS 192 | run: | 193 | branch_or_tag_name=${GITHUB_REF#refs/*/} 194 | ! test -d ebin || touch ebin/* 195 | make ct-msg \ 196 | base_rmq_ref=master \ 197 | current_rmq_ref=$branch_or_tag_name \ 198 | FULL= \ 199 | FAIL_FAST=1 \ 200 | SKIP_AS_ERROR=1 201 | - name: CACHE SECONDARY UMBRELLAS 202 | if: success() && 'oldest' == 'oldest' 203 | uses: actions/cache@v1 204 | with: 205 | path: umbrellas 206 | key: secondary-umbrellas-v3.7.26-v3.8.3-erlang-21.3-rev3 207 | - name: VERIFY SECONDARY UMBRELLA COPIES (in case of cache restore failure) 208 | if: success() && 'oldest' == 'oldest' 209 | run: | 210 | set -x 211 | for version in v3.7.26 v3.8.3; do 212 | umbrella="umbrellas/$version" 213 | if ! test -d "$umbrella" || 214 | ! make -C "$umbrella/deps/amqp10_client" test-dist; then 215 | rm -rf "$umbrella" 216 | 217 | # Fetch the master Umbrella; the final umbrellas are created from 218 | # the master copy. 219 | if ! test -d umbrellas/master; then 220 | git config --global advice.detachedHead false 221 | git clone \ 222 | https://github.com/rabbitmq/rabbitmq-public-umbrella.git \ 223 | umbrellas/master 224 | make -C umbrellas/master co # To get RabbitMQ components. 225 | fi 226 | 227 | # We copy the master Umbrella and checkout the appropriate tag. 228 | cp -a umbrellas/master "$umbrella" 229 | git -C "$umbrella" checkout "master" 230 | make -C "$umbrella" up BRANCH="$version" 231 | # To remove third-party deps which were checked out when the 232 | # projects were on the `master` branch. Thus, possibly not the 233 | # version pinning we expect. We update the Umbrella one last time 234 | # to fetch the correct third-party deps. 235 | make -C "$umbrella" clean-3rd-party-repos 236 | make -C "$umbrella" up 237 | make -C "$umbrella/deps/amqp10_client" test-dist 238 | rm -fr "$umbrella"/deps/*/{.git,test} "$umbrella"/.git 239 | fi 240 | done 241 | rm -fr umbrellas/master 242 | - name: RUN TESTS [mixed-versions] 243 | if: success() && 'oldest' == 'oldest' 244 | run: | 245 | set -x 246 | branch_or_tag_name=${GITHUB_REF#refs/*/} 247 | for umbrella in umbrellas/*; do 248 | test -d "$umbrella" 249 | printf '\n\033[1;32mMixing clusters with RabbitMQ %s\033[0m' \ 250 | $(basename "$umbrella") 251 | make distclean-ct ct-msg \ 252 | base_rmq_ref=master \ 253 | current_rmq_ref=$branch_or_tag_name \ 254 | FULL= \ 255 | FAIL_FAST=1 \ 256 | SKIP_AS_ERROR=1 \ 257 | SECONDARY_UMBRELLA=$PWD/$umbrella \ 258 | RABBITMQ_FEATURE_FLAGS= 259 | done 260 | - name: ON FAILURE ARCHIVE TESTS LOGS 261 | if: failure() 262 | run: | 263 | make ct-logs-archive 264 | - name: ON FAILURE UPLOAD TESTS LOGS ARTIFACT 265 | # https://github.com/marketplace/actions/upload-artifact 266 | uses: actions/upload-artifact@v2-preview 267 | if: failure() 268 | with: 269 | name: ct-msg-logs 270 | path: "*-ct-logs-*.tar.xz" 271 | # vim:sw=2:et: 272 | ct-system: 273 | needs: [checks] 274 | # https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts 275 | name: ct-system 276 | runs-on: ubuntu-18.04 277 | steps: 278 | - name: CHECKOUT REPOSITORY 279 | uses: actions/checkout@v2 280 | # https://github.com/marketplace/actions/setup-elixir 281 | - name: CONFIGURE OTP & ELIXIR 282 | uses: actions/setup-elixir@v1 283 | with: 284 | otp-version: 21.3 285 | # https://github.com/elixir-lang/elixir/releases 286 | elixir-version: 1.10.4 287 | - name: CACHE DEPS 288 | uses: actions/cache@v1 289 | with: 290 | path: deps 291 | key: otp-21.3_git-${{ github.sha }}_deps 292 | - name: RUN TESTS 293 | run: | 294 | branch_or_tag_name=${GITHUB_REF#refs/*/} 295 | ! test -d ebin || touch ebin/* 296 | make ct-system \ 297 | base_rmq_ref=master \ 298 | current_rmq_ref=$branch_or_tag_name \ 299 | FULL= \ 300 | FAIL_FAST=1 \ 301 | SKIP_AS_ERROR=1 302 | - name: CACHE SECONDARY UMBRELLAS 303 | if: success() && 'oldest' == 'oldest' 304 | uses: actions/cache@v1 305 | with: 306 | path: umbrellas 307 | key: secondary-umbrellas-v3.7.26-v3.8.3-erlang-21.3-rev3 308 | - name: VERIFY SECONDARY UMBRELLA COPIES (in case of cache restore failure) 309 | if: success() && 'oldest' == 'oldest' 310 | run: | 311 | set -x 312 | for version in v3.7.26 v3.8.3; do 313 | umbrella="umbrellas/$version" 314 | if ! test -d "$umbrella" || 315 | ! make -C "$umbrella/deps/amqp10_client" test-dist; then 316 | rm -rf "$umbrella" 317 | 318 | # Fetch the master Umbrella; the final umbrellas are created from 319 | # the master copy. 320 | if ! test -d umbrellas/master; then 321 | git config --global advice.detachedHead false 322 | git clone \ 323 | https://github.com/rabbitmq/rabbitmq-public-umbrella.git \ 324 | umbrellas/master 325 | make -C umbrellas/master co # To get RabbitMQ components. 326 | fi 327 | 328 | # We copy the master Umbrella and checkout the appropriate tag. 329 | cp -a umbrellas/master "$umbrella" 330 | git -C "$umbrella" checkout "master" 331 | make -C "$umbrella" up BRANCH="$version" 332 | # To remove third-party deps which were checked out when the 333 | # projects were on the `master` branch. Thus, possibly not the 334 | # version pinning we expect. We update the Umbrella one last time 335 | # to fetch the correct third-party deps. 336 | make -C "$umbrella" clean-3rd-party-repos 337 | make -C "$umbrella" up 338 | make -C "$umbrella/deps/amqp10_client" test-dist 339 | rm -fr "$umbrella"/deps/*/{.git,test} "$umbrella"/.git 340 | fi 341 | done 342 | rm -fr umbrellas/master 343 | - name: RUN TESTS [mixed-versions] 344 | if: success() && 'oldest' == 'oldest' 345 | run: | 346 | set -x 347 | branch_or_tag_name=${GITHUB_REF#refs/*/} 348 | for umbrella in umbrellas/*; do 349 | test -d "$umbrella" 350 | printf '\n\033[1;32mMixing clusters with RabbitMQ %s\033[0m' \ 351 | $(basename "$umbrella") 352 | make distclean-ct ct-system \ 353 | base_rmq_ref=master \ 354 | current_rmq_ref=$branch_or_tag_name \ 355 | FULL= \ 356 | FAIL_FAST=1 \ 357 | SKIP_AS_ERROR=1 \ 358 | SECONDARY_UMBRELLA=$PWD/$umbrella \ 359 | RABBITMQ_FEATURE_FLAGS= 360 | done 361 | - name: ON FAILURE ARCHIVE TESTS LOGS 362 | if: failure() 363 | run: | 364 | make ct-logs-archive 365 | - name: ON FAILURE UPLOAD TESTS LOGS ARTIFACT 366 | # https://github.com/marketplace/actions/upload-artifact 367 | uses: actions/upload-artifact@v2-preview 368 | if: failure() 369 | with: 370 | name: ct-system-logs 371 | path: "*-ct-logs-*.tar.xz" 372 | # vim:sw=2:et: 373 | capture-tested-deps-versions: 374 | needs: 375 | - dialyzer 376 | - eunit 377 | - ct-msg 378 | - ct-system 379 | runs-on: ubuntu-18.04 380 | steps: 381 | - name: CHECKOUT REPOSITORY 382 | uses: actions/checkout@v2 383 | - name: CACHE DEPS 384 | uses: actions/cache@v1 385 | with: 386 | path: deps 387 | key: otp-21.3_git-${{ github.sha }}_deps 388 | - name: FORMAT GIT REF 389 | # https://github.community/t5/GitHub-Actions/How-can-I-set-an-expression-as-an-environment-variable-at/m-p/41804/highlight/true#M4751 390 | id: ref 391 | run: | 392 | branch_or_tag_name=${GITHUB_REF#refs/*/} 393 | echo "::set-output name=branch_or_tag_name::$branch_or_tag_name" 394 | - name: UPLOAD TO S3 395 | if: github.ref == 'refs/heads/master' 396 | # https://github.com/marketplace/actions/s3-file-upload 397 | uses: zdurham/s3-upload-github-action@master 398 | env: 399 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 400 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 401 | AWS_REGION: ${{ secrets.AWS_REGION }} 402 | FILE: deps/amqp10_client-rabbitmq-deps.mk 403 | S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 404 | S3_KEY: rabbitmq-amqp1.0-client/${{ steps.ref.outputs.branch_or_tag_name }}/${{ github.run_id }}/otp-21.3/amqp10_client-rabbitmq-deps.mk 405 | -------------------------------------------------------------------------------- /.github/workflows/test-erlang-otp-22.3.yaml: -------------------------------------------------------------------------------- 1 | # vim:sw=2:et: 2 | # https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow 3 | name: "Test - Erlang 22.3" 4 | on: 5 | push: 6 | repository_dispatch: 7 | types: 8 | - new-commit-to-dep-release-branch 9 | jobs: 10 | # vim:sw=2:et: 11 | checks: 12 | name: checks 13 | runs-on: ubuntu-18.04 14 | steps: 15 | - name: CHECKOUT REPOSITORY 16 | uses: actions/checkout@v2 17 | # https://github.com/marketplace/actions/setup-elixir 18 | - name: CONFIGURE OTP & ELIXIR 19 | uses: actions/setup-elixir@v1 20 | with: 21 | otp-version: 22.3 22 | # https://github.com/elixir-lang/elixir/releases 23 | elixir-version: 1.10.4 24 | - name: CHECK RABBITMQ COMPONENTS 25 | # https://github.community/t5/GitHub-Actions/How-can-I-set-an-expression-as-an-environment-variable-at/m-p/41804/highlight/true#M4751 26 | id: ref 27 | run: | 28 | branch_or_tag_name=${GITHUB_REF#refs/*/} 29 | echo "::set-output name=branch_or_tag_name::$branch_or_tag_name" 30 | make check-rabbitmq-components.mk base_rmq_ref=master current_rmq_ref=$branch_or_tag_name 31 | # https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows 32 | - name: CACHE DEPS 33 | uses: actions/cache@v1 34 | with: 35 | path: deps 36 | key: otp-22.3_git-${{ github.sha }}_deps 37 | - name: RESOLVE & COMPILE DEPS 38 | run: | 39 | make deps test-deps base_rmq_ref=master current_rmq_ref=${{ steps.ref.outputs.branch_or_tag_name }} 40 | echo "Capture versions of the RabbitMQ components used in this workflow..." 41 | make amqp10_client-rabbitmq-deps.mk 42 | mv amqp10_client-rabbitmq-deps.mk deps/ 43 | echo "Remove directories not used in the subsequent jobs..." 44 | rm -fr deps/*/{.git,test} 45 | - name: UPLOAD DEPS VERSIONS 46 | uses: actions/upload-artifact@v2-preview 47 | with: 48 | name: amqp10_client-rabbitmq-deps.mk 49 | path: deps/amqp10_client-rabbitmq-deps.mk 50 | - name: CHECK CROSS REFERENCES 51 | run: | 52 | make xref base_rmq_ref=master current_rmq_ref=${{ steps.ref.outputs.branch_or_tag_name }} 53 | - name: COMPILE FOR TEST 54 | run: | 55 | make test-build base_rmq_ref=master current_rmq_ref=${{ steps.ref.outputs.branch_or_tag_name }} 56 | - name: CACHE SECONDARY UMBRELLAS 57 | if: success() && 'latest' == 'oldest' 58 | uses: actions/cache@v1 59 | with: 60 | path: umbrellas 61 | key: secondary-umbrellas-v3.7.26-v3.8.3-erlang-22.3-rev3 62 | - name: PREPARE SECONDARY UMBRELLA COPIES 63 | if: success() && 'latest' == 'oldest' 64 | run: | 65 | # ---------------------------------------------------------- 66 | # CAUTION: 67 | # The same script must be copied to `03-CT_SUITE.yaml`. It is used to 68 | # recreate the umbrellas if the cache restore fails. 69 | # ---------------------------------------------------------- 70 | set -x 71 | for version in v3.7.26 v3.8.3; do 72 | umbrella="umbrellas/$version" 73 | if ! test -d "$umbrella" || 74 | ! make -C "$umbrella/deps/amqp10_client" test-dist; then 75 | rm -rf "$umbrella" 76 | 77 | # Fetch the master Umbrella; the final umbrellas are created from 78 | # the master copy. 79 | if ! test -d umbrellas/master; then 80 | git config --global advice.detachedHead false 81 | git clone \ 82 | https://github.com/rabbitmq/rabbitmq-public-umbrella.git \ 83 | umbrellas/master 84 | make -C umbrellas/master co # To get RabbitMQ components. 85 | fi 86 | 87 | # We copy the master Umbrella and checkout the appropriate tag. 88 | cp -a umbrellas/master "$umbrella" 89 | git -C "$umbrella" checkout "master" 90 | make -C "$umbrella" up BRANCH="$version" 91 | # To remove third-party deps which were checked out when the 92 | # projects were on the `master` branch. Thus, possibly not the 93 | # version pinning we expect. We update the Umbrella one last time 94 | # to fetch the correct third-party deps. 95 | make -C "$umbrella" clean-3rd-party-repos 96 | make -C "$umbrella" up 97 | make -C "$umbrella/deps/amqp10_client" test-dist 98 | rm -fr "$umbrella"/deps/*/{.git,test} "$umbrella"/.git 99 | fi 100 | done 101 | rm -fr umbrellas/master 102 | # vim:sw=2:et: 103 | dialyzer: 104 | name: dialyzer 105 | needs: [checks] 106 | runs-on: ubuntu-18.04 107 | steps: 108 | - name: CHECKOUT REPOSITORY 109 | if: success() && 'latest' == 'latest' 110 | uses: actions/checkout@v2 111 | # https://github.com/marketplace/actions/setup-elixir 112 | - name: CONFIGURE OTP & ELIXIR 113 | if: success() && 'latest' == 'latest' 114 | uses: actions/setup-elixir@v1 115 | with: 116 | otp-version: 22.3 117 | # https://github.com/elixir-lang/elixir/releases 118 | elixir-version: 1.10.4 119 | - name: CACHE DEPS 120 | if: success() && 'latest' == 'latest' 121 | uses: actions/cache@v1 122 | with: 123 | path: deps 124 | key: otp-22.3_git-${{ github.sha }}_deps 125 | - name: CACHE DIALYZER PLT 126 | if: success() && 'latest' == 'latest' 127 | uses: actions/cache@v1 128 | with: 129 | path: .amqp10_client.plt 130 | key: plt-amqp10_client-erlang-22.3-g${{ github.sha }} 131 | - name: RUN DIALYZER 132 | if: success() && 'latest' == 'latest' 133 | run: | 134 | branch_or_tag_name=${GITHUB_REF#refs/*/} 135 | make dialyze \ 136 | base_rmq_ref=master \ 137 | current_rmq_ref=$branch_or_tag_name \ 138 | FULL= 139 | # vim:sw=2:et: 140 | eunit: 141 | needs: [checks] 142 | # https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts 143 | name: eunit 144 | runs-on: ubuntu-18.04 145 | steps: 146 | - name: CHECKOUT REPOSITORY 147 | uses: actions/checkout@v2 148 | # https://github.com/marketplace/actions/setup-elixir 149 | - name: CONFIGURE OTP & ELIXIR 150 | uses: actions/setup-elixir@v1 151 | with: 152 | otp-version: 22.3 153 | # https://github.com/elixir-lang/elixir/releases 154 | elixir-version: 1.10.4 155 | - name: CACHE DEPS 156 | uses: actions/cache@v1 157 | with: 158 | path: deps 159 | key: otp-22.3_git-${{ github.sha }}_deps 160 | - name: RUN TESTS 161 | run: | 162 | ! test -d ebin || touch ebin/* 163 | branch_or_tag_name=${GITHUB_REF#refs/*/} 164 | make eunit \ 165 | base_rmq_ref=master \ 166 | current_rmq_ref=$branch_or_tag_name \ 167 | FULL= \ 168 | FAIL_FAST=1 \ 169 | SKIP_AS_ERROR=1 170 | # vim:sw=2:et: 171 | ct-msg: 172 | needs: [checks] 173 | # https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts 174 | name: ct-msg 175 | runs-on: ubuntu-18.04 176 | steps: 177 | - name: CHECKOUT REPOSITORY 178 | uses: actions/checkout@v2 179 | # https://github.com/marketplace/actions/setup-elixir 180 | - name: CONFIGURE OTP & ELIXIR 181 | uses: actions/setup-elixir@v1 182 | with: 183 | otp-version: 22.3 184 | # https://github.com/elixir-lang/elixir/releases 185 | elixir-version: 1.10.4 186 | - name: CACHE DEPS 187 | uses: actions/cache@v1 188 | with: 189 | path: deps 190 | key: otp-22.3_git-${{ github.sha }}_deps 191 | - name: RUN TESTS 192 | run: | 193 | branch_or_tag_name=${GITHUB_REF#refs/*/} 194 | ! test -d ebin || touch ebin/* 195 | make ct-msg \ 196 | base_rmq_ref=master \ 197 | current_rmq_ref=$branch_or_tag_name \ 198 | FULL= \ 199 | FAIL_FAST=1 \ 200 | SKIP_AS_ERROR=1 201 | - name: CACHE SECONDARY UMBRELLAS 202 | if: success() && 'latest' == 'oldest' 203 | uses: actions/cache@v1 204 | with: 205 | path: umbrellas 206 | key: secondary-umbrellas-v3.7.26-v3.8.3-erlang-22.3-rev3 207 | - name: VERIFY SECONDARY UMBRELLA COPIES (in case of cache restore failure) 208 | if: success() && 'latest' == 'oldest' 209 | run: | 210 | set -x 211 | for version in v3.7.26 v3.8.3; do 212 | umbrella="umbrellas/$version" 213 | if ! test -d "$umbrella" || 214 | ! make -C "$umbrella/deps/amqp10_client" test-dist; then 215 | rm -rf "$umbrella" 216 | 217 | # Fetch the master Umbrella; the final umbrellas are created from 218 | # the master copy. 219 | if ! test -d umbrellas/master; then 220 | git config --global advice.detachedHead false 221 | git clone \ 222 | https://github.com/rabbitmq/rabbitmq-public-umbrella.git \ 223 | umbrellas/master 224 | make -C umbrellas/master co # To get RabbitMQ components. 225 | fi 226 | 227 | # We copy the master Umbrella and checkout the appropriate tag. 228 | cp -a umbrellas/master "$umbrella" 229 | git -C "$umbrella" checkout "master" 230 | make -C "$umbrella" up BRANCH="$version" 231 | # To remove third-party deps which were checked out when the 232 | # projects were on the `master` branch. Thus, possibly not the 233 | # version pinning we expect. We update the Umbrella one last time 234 | # to fetch the correct third-party deps. 235 | make -C "$umbrella" clean-3rd-party-repos 236 | make -C "$umbrella" up 237 | make -C "$umbrella/deps/amqp10_client" test-dist 238 | rm -fr "$umbrella"/deps/*/{.git,test} "$umbrella"/.git 239 | fi 240 | done 241 | rm -fr umbrellas/master 242 | - name: RUN TESTS [mixed-versions] 243 | if: success() && 'latest' == 'oldest' 244 | run: | 245 | set -x 246 | branch_or_tag_name=${GITHUB_REF#refs/*/} 247 | for umbrella in umbrellas/*; do 248 | test -d "$umbrella" 249 | printf '\n\033[1;32mMixing clusters with RabbitMQ %s\033[0m' \ 250 | $(basename "$umbrella") 251 | make distclean-ct ct-msg \ 252 | base_rmq_ref=master \ 253 | current_rmq_ref=$branch_or_tag_name \ 254 | FULL= \ 255 | FAIL_FAST=1 \ 256 | SKIP_AS_ERROR=1 \ 257 | SECONDARY_UMBRELLA=$PWD/$umbrella \ 258 | RABBITMQ_FEATURE_FLAGS= 259 | done 260 | - name: ON FAILURE ARCHIVE TESTS LOGS 261 | if: failure() 262 | run: | 263 | make ct-logs-archive 264 | - name: ON FAILURE UPLOAD TESTS LOGS ARTIFACT 265 | # https://github.com/marketplace/actions/upload-artifact 266 | uses: actions/upload-artifact@v2-preview 267 | if: failure() 268 | with: 269 | name: ct-msg-logs 270 | path: "*-ct-logs-*.tar.xz" 271 | # vim:sw=2:et: 272 | ct-system: 273 | needs: [checks] 274 | # https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts 275 | name: ct-system 276 | runs-on: ubuntu-18.04 277 | steps: 278 | - name: CHECKOUT REPOSITORY 279 | uses: actions/checkout@v2 280 | # https://github.com/marketplace/actions/setup-elixir 281 | - name: CONFIGURE OTP & ELIXIR 282 | uses: actions/setup-elixir@v1 283 | with: 284 | otp-version: 22.3 285 | # https://github.com/elixir-lang/elixir/releases 286 | elixir-version: 1.10.4 287 | - name: CACHE DEPS 288 | uses: actions/cache@v1 289 | with: 290 | path: deps 291 | key: otp-22.3_git-${{ github.sha }}_deps 292 | - name: RUN TESTS 293 | run: | 294 | branch_or_tag_name=${GITHUB_REF#refs/*/} 295 | ! test -d ebin || touch ebin/* 296 | make ct-system \ 297 | base_rmq_ref=master \ 298 | current_rmq_ref=$branch_or_tag_name \ 299 | FULL= \ 300 | FAIL_FAST=1 \ 301 | SKIP_AS_ERROR=1 302 | - name: CACHE SECONDARY UMBRELLAS 303 | if: success() && 'latest' == 'oldest' 304 | uses: actions/cache@v1 305 | with: 306 | path: umbrellas 307 | key: secondary-umbrellas-v3.7.26-v3.8.3-erlang-22.3-rev3 308 | - name: VERIFY SECONDARY UMBRELLA COPIES (in case of cache restore failure) 309 | if: success() && 'latest' == 'oldest' 310 | run: | 311 | set -x 312 | for version in v3.7.26 v3.8.3; do 313 | umbrella="umbrellas/$version" 314 | if ! test -d "$umbrella" || 315 | ! make -C "$umbrella/deps/amqp10_client" test-dist; then 316 | rm -rf "$umbrella" 317 | 318 | # Fetch the master Umbrella; the final umbrellas are created from 319 | # the master copy. 320 | if ! test -d umbrellas/master; then 321 | git config --global advice.detachedHead false 322 | git clone \ 323 | https://github.com/rabbitmq/rabbitmq-public-umbrella.git \ 324 | umbrellas/master 325 | make -C umbrellas/master co # To get RabbitMQ components. 326 | fi 327 | 328 | # We copy the master Umbrella and checkout the appropriate tag. 329 | cp -a umbrellas/master "$umbrella" 330 | git -C "$umbrella" checkout "master" 331 | make -C "$umbrella" up BRANCH="$version" 332 | # To remove third-party deps which were checked out when the 333 | # projects were on the `master` branch. Thus, possibly not the 334 | # version pinning we expect. We update the Umbrella one last time 335 | # to fetch the correct third-party deps. 336 | make -C "$umbrella" clean-3rd-party-repos 337 | make -C "$umbrella" up 338 | make -C "$umbrella/deps/amqp10_client" test-dist 339 | rm -fr "$umbrella"/deps/*/{.git,test} "$umbrella"/.git 340 | fi 341 | done 342 | rm -fr umbrellas/master 343 | - name: RUN TESTS [mixed-versions] 344 | if: success() && 'latest' == 'oldest' 345 | run: | 346 | set -x 347 | branch_or_tag_name=${GITHUB_REF#refs/*/} 348 | for umbrella in umbrellas/*; do 349 | test -d "$umbrella" 350 | printf '\n\033[1;32mMixing clusters with RabbitMQ %s\033[0m' \ 351 | $(basename "$umbrella") 352 | make distclean-ct ct-system \ 353 | base_rmq_ref=master \ 354 | current_rmq_ref=$branch_or_tag_name \ 355 | FULL= \ 356 | FAIL_FAST=1 \ 357 | SKIP_AS_ERROR=1 \ 358 | SECONDARY_UMBRELLA=$PWD/$umbrella \ 359 | RABBITMQ_FEATURE_FLAGS= 360 | done 361 | - name: ON FAILURE ARCHIVE TESTS LOGS 362 | if: failure() 363 | run: | 364 | make ct-logs-archive 365 | - name: ON FAILURE UPLOAD TESTS LOGS ARTIFACT 366 | # https://github.com/marketplace/actions/upload-artifact 367 | uses: actions/upload-artifact@v2-preview 368 | if: failure() 369 | with: 370 | name: ct-system-logs 371 | path: "*-ct-logs-*.tar.xz" 372 | # vim:sw=2:et: 373 | capture-tested-deps-versions: 374 | needs: 375 | - dialyzer 376 | - eunit 377 | - ct-msg 378 | - ct-system 379 | runs-on: ubuntu-18.04 380 | steps: 381 | - name: CHECKOUT REPOSITORY 382 | uses: actions/checkout@v2 383 | - name: CACHE DEPS 384 | uses: actions/cache@v1 385 | with: 386 | path: deps 387 | key: otp-22.3_git-${{ github.sha }}_deps 388 | - name: FORMAT GIT REF 389 | # https://github.community/t5/GitHub-Actions/How-can-I-set-an-expression-as-an-environment-variable-at/m-p/41804/highlight/true#M4751 390 | id: ref 391 | run: | 392 | branch_or_tag_name=${GITHUB_REF#refs/*/} 393 | echo "::set-output name=branch_or_tag_name::$branch_or_tag_name" 394 | - name: UPLOAD TO S3 395 | if: github.ref == 'refs/heads/master' 396 | # https://github.com/marketplace/actions/s3-file-upload 397 | uses: zdurham/s3-upload-github-action@master 398 | env: 399 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 400 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 401 | AWS_REGION: ${{ secrets.AWS_REGION }} 402 | FILE: deps/amqp10_client-rabbitmq-deps.mk 403 | S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 404 | S3_KEY: rabbitmq-amqp1.0-client/${{ steps.ref.outputs.branch_or_tag_name }}/${{ github.run_id }}/otp-22.3/amqp10_client-rabbitmq-deps.mk 405 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sw? 2 | .*.sw? 3 | *.beam 4 | *.plt 5 | /.erlang.mk/ 6 | /cover/ 7 | /deps/ 8 | /doc/ 9 | /ebin/ 10 | /escript/ 11 | /escript.lock 12 | /logs/ 13 | /plugins/ 14 | /plugins.lock 15 | /sbin/ 16 | /sbin.lock 17 | /xrefr 18 | elvis 19 | 20 | amqp10_client.d 21 | /*.coverdata 22 | 23 | # Generated source files. 24 | /include/rabbit_amqp1_0_framing.hrl 25 | /src/rabbit_amqp1_0_framing0.erl 26 | 27 | # Downloaded ActiveMQ. 28 | /test/system_SUITE_data/apache-activemq-* 29 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open 4 | and welcoming community, we pledge to respect all people who contribute through reporting 5 | issues, posting feature requests, updating documentation, submitting pull requests or 6 | patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for 9 | everyone, regardless of level of experience, gender, gender identity and expression, 10 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 11 | religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 24 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 25 | Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors 26 | that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing this project. Project 30 | maintainers who do not follow or enforce the Code of Conduct may be permanently removed 31 | from the project team. 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will 38 | be reviewed and investigated and will result in a response that is deemed necessary and 39 | appropriate to the circumstances. Maintainers are obligated to maintain confidentiality 40 | with regard to the reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the 43 | [Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at 44 | [contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/) 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for using RabbitMQ and for taking the time to contribute to the project. 2 | This document has two main parts: 3 | 4 | * when and how to file GitHub issues for RabbitMQ projects 5 | * how to submit pull requests 6 | 7 | They intend to save you and RabbitMQ maintainers some time, so please 8 | take a moment to read through them. 9 | 10 | ## Overview 11 | 12 | ### GitHub issues 13 | 14 | The RabbitMQ team uses GitHub issues for _specific actionable items_ that 15 | engineers can work on. This assumes the following: 16 | 17 | * GitHub issues are not used for questions, investigations, root cause 18 | analysis, discussions of potential issues, etc (as defined by this team) 19 | * Enough information is provided by the reporter for maintainers to work with 20 | 21 | The team receives many questions through various venues every single 22 | day. Frequently, these questions do not include the necessary details 23 | the team needs to begin useful work. GitHub issues can very quickly 24 | turn into a something impossible to navigate and make sense 25 | of. Because of this, questions, investigations, root cause analysis, 26 | and discussions of potential features are all considered to be 27 | [mailing list][rmq-users] material. If you are unsure where to begin, 28 | the [RabbitMQ users mailing list][rmq-users] is the right place. 29 | 30 | Getting all the details necessary to reproduce an issue, make a 31 | conclusion or even form a hypothesis about what's happening can take a 32 | fair amount of time. Please help others help you by providing a way to 33 | reproduce the behavior you're observing, or at least sharing as much 34 | relevant information as possible on the [RabbitMQ users mailing 35 | list][rmq-users]. 36 | 37 | Please provide versions of the software used: 38 | 39 | * RabbitMQ server 40 | * Erlang 41 | * Operating system version (and distribution, if applicable) 42 | * All client libraries used 43 | * RabbitMQ plugins (if applicable) 44 | 45 | The following information greatly helps in investigating and reproducing issues: 46 | 47 | * RabbitMQ server logs 48 | * A code example or terminal transcript that can be used to reproduce 49 | * Full exception stack traces (a single line message is not enough!) 50 | * `rabbitmqctl report` and `rabbitmqctl environment` output 51 | * Other relevant details about the environment and workload, e.g. a traffic capture 52 | * Feel free to edit out hostnames and other potentially sensitive information. 53 | 54 | To make collecting much of this and other environment information, use 55 | the [`rabbitmq-collect-env`][rmq-collect-env] script. It will produce an archive with 56 | server logs, operating system logs, output of certain diagnostics commands and so on. 57 | Please note that **no effort is made to scrub any information that may be sensitive**. 58 | 59 | ### Pull Requests 60 | 61 | RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. 62 | Pull requests is the primary place of discussing code changes. 63 | 64 | Here's the recommended workflow: 65 | 66 | * [Fork the repository][github-fork] or repositories you plan on contributing to. If multiple 67 | repositories are involved in addressing the same issue, please use the same branch name 68 | in each repository 69 | * Create a branch with a descriptive name in the relevant repositories 70 | * Make your changes, run tests (usually with `make tests`), commit with a 71 | [descriptive message][git-commit-msgs], push to your fork 72 | * Submit pull requests with an explanation what has been changed and **why** 73 | * Submit a filled out and signed [Contributor Agreement][ca-agreement] if needed (see below) 74 | * Be patient. We will get to your pull request eventually 75 | 76 | If what you are going to work on is a substantial change, please first 77 | ask the core team for their opinion on the [RabbitMQ users mailing list][rmq-users]. 78 | 79 | ## Running Tests 80 | 81 | make tests 82 | 83 | will run all suites. 84 | 85 | ## Code of Conduct 86 | 87 | See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). 88 | 89 | ## Contributor Agreement 90 | 91 | If you want to contribute a non-trivial change, please submit a signed 92 | copy of our [Contributor Agreement][ca-agreement] around the time you 93 | submit your pull request. This will make it much easier (in some 94 | cases, possible) for the RabbitMQ team at Pivotal to merge your 95 | contribution. 96 | 97 | ## Where to Ask Questions 98 | 99 | If something isn't clear, feel free to ask on our [mailing list][rmq-users]. 100 | 101 | [rmq-collect-env]: https://github.com/rabbitmq/support-tools/blob/master/scripts/rabbitmq-collect-env 102 | [git-commit-msgs]: https://chris.beams.io/posts/git-commit/ 103 | [rmq-users]: https://groups.google.com/forum/#!forum/rabbitmq-users 104 | [ca-agreement]: https://cla.pivotal.io/sign/rabbitmq 105 | [github-fork]: https://help.github.com/articles/fork-a-repo/ 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This package is licensed under the MPL 2.0. For the MPL 2.0, please see LICENSE-MPL-RabbitMQ. 2 | 3 | If you have any questions regarding licensing, please contact us at 4 | info@rabbitmq.com. 5 | -------------------------------------------------------------------------------- /LICENSE-MPL-RabbitMQ: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = amqp10_client 2 | PROJECT_DESCRIPTION = AMQP 1.0 client from the RabbitMQ Project 3 | PROJECT_MOD = amqp10_client_app 4 | 5 | BUILD_DEPS = rabbit_common elvis_mk 6 | DEPS = amqp10_common 7 | TEST_DEPS = rabbit rabbitmq_amqp1_0 rabbitmq_ct_helpers 8 | LOCAL_DEPS = ssl inets crypto 9 | 10 | DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-test.mk 11 | DEP_PLUGINS = rabbit_common/mk/rabbitmq-macros.mk \ 12 | rabbit_common/mk/rabbitmq-hexpm.mk \ 13 | rabbit_common/mk/rabbitmq-dist.mk \ 14 | rabbit_common/mk/rabbitmq-run.mk \ 15 | rabbit_common/mk/rabbitmq-test.mk \ 16 | rabbit_common/mk/rabbitmq-tools.mk \ 17 | rabbit_common/mk/rabbitmq-github-actions.mk 18 | 19 | DEP_PLUGINS += elvis_mk 20 | dep_elvis_mk = git https://github.com/inaka/elvis.mk.git master 21 | 22 | # FIXME: Use erlang.mk patched for RabbitMQ, while waiting for PRs to be 23 | # reviewed and merged. 24 | 25 | ERLANG_MK_REPO = https://github.com/rabbitmq/erlang.mk.git 26 | ERLANG_MK_COMMIT = rabbitmq-tmp 27 | 28 | include rabbitmq-components.mk 29 | include erlang.mk 30 | 31 | # -------------------------------------------------------------------- 32 | # Compiler flags. 33 | # -------------------------------------------------------------------- 34 | 35 | # gen_fsm is deprecated starting from Erlang 20, but we want to support 36 | # Erlang 19 as well. 37 | 38 | ERTS_VER := $(shell erl -version 2>&1 | sed -E 's/.* version //') 39 | ERLANG_20_ERTS_VER := 9.0 40 | 41 | ifeq ($(call compare_version,$(ERTS_VER),$(ERLANG_20_ERTS_VER),>=),true) 42 | ERLC_OPTS += -Dnowarn_deprecated_gen_fsm 43 | endif 44 | 45 | # Dialyze the tests. 46 | DIALYZER_OPTS += --src -r test 47 | 48 | # -------------------------------------------------------------------- 49 | # ActiveMQ for the testsuite. 50 | # -------------------------------------------------------------------- 51 | 52 | ACTIVEMQ_VERSION := 5.14.4 53 | ACTIVEMQ_URL := 'https://archive.apache.org/dist/activemq/$(ACTIVEMQ_VERSION)/apache-activemq-$(ACTIVEMQ_VERSION)-bin.tar.gz' 54 | 55 | ACTIVEMQ := $(abspath test/system_SUITE_data/apache-activemq-$(ACTIVEMQ_VERSION)/bin/activemq) 56 | export ACTIVEMQ 57 | 58 | $(ACTIVEMQ): \ 59 | test/system_SUITE_data/apache-activemq-$(ACTIVEMQ_VERSION)-bin.tar.gz 60 | $(gen_verbose) cd "$(dir $<)" && tar zxf "$(notdir $<)" 61 | 62 | test/system_SUITE_data/apache-activemq-$(ACTIVEMQ_VERSION)-bin.tar.gz: 63 | $(gen_verbose) $(call core_http_get,$@,$(ACTIVEMQ_URL)) 64 | 65 | tests:: $(ACTIVEMQ) 66 | 67 | ct ct-system: $(ACTIVEMQ) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Erlang AMQP 1.0 client 2 | 3 | ## This was migrated to https://github.com/rabbitmq/rabbitmq-server 4 | 5 | This repository has been moved to the main unified RabbitMQ "monorepo", including all open issues. You can find the source under [/deps/amqp10_client](https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/amqp10_client). 6 | All issues have been transferred. 7 | 8 | ## Overview 9 | 10 | This is an [Erlang client for the AMQP 1.0](https://www.amqp.org/resources/specifications) protocol. 11 | 12 | It's primary purpose is to be used in RabbitMQ related projects but it is a 13 | generic client that was tested with at least 4 implementations of AMQP 1.0. 14 | 15 | If you are looking for an Erlang client for [AMQP 0-9-1](https://www.rabbitmq.com/tutorials/amqp-concepts.html) — a completely different 16 | protocol despite the name — [consider this one](https://github.com/rabbitmq/rabbitmq-erlang-client). 17 | 18 | ## Project Maturity and Status 19 | 20 | This client is used in the cross-protocol version of the RabbitMQ Shovel plugin. It is not 100% 21 | feature complete but moderately mature and was tested against at least three AMQP 1.0 servers: 22 | RabbitMQ, Azure ServiceBus, ActiveMQ. 23 | 24 | This client library is not officially supported by VMware at this time. 25 | 26 | ## Usage 27 | 28 | ### Connection Settings 29 | 30 | The `connection_config` map contains various configuration properties. 31 | 32 | ``` 33 | -type address :: inet:socket_address() | inet:hostname(). 34 | 35 | -type connection_config() :: 36 | #{container_id => binary(), % mandatory 37 | %% must provide a list of addresses or a single address 38 | addresses => [address()], 39 | address => address(), 40 | %% defaults to 5672, mandatory for TLS 41 | port => inet:port_number(), 42 | % the dns name of the target host 43 | % required by some vendors such as Azure ServiceBus 44 | hostname => binary(), 45 | tls_opts => {secure_port, [ssl:ssl_option()]}, % optional 46 | notify => pid(), % Pid to receive protocol notifications. Set to self() if not provided 47 | max_frame_size => non_neg_integer(), % incoming max frame size 48 | idle_time_out => non_neg_integer(), % heartbeat 49 | sasl => none | anon | {plain, User :: binary(), Password :: binary(), 50 | % set this to a negative value to allow a sender to "overshoot" the flow 51 | % control by this margin 52 | transfer_limit_margin => 0 | neg_integer()} 53 | }. 54 | 55 | ``` 56 | 57 | ### TLS 58 | 59 | TLS is enabled by setting the `tls_opts` connection configuration property. 60 | Currently the only valid value is `{secure_port, [ssl_option]}` where the port 61 | specified only accepts TLS. It is possible that tls negotiation as described 62 | in the amqp 1.0 protocol will be supported in the future. If no value is provided 63 | for `tls_opt` then a plain socket will be used. 64 | 65 | 66 | ### Basic Example 67 | 68 | ``` 69 | %% this will connect to a localhost node 70 | {ok, Hostname} = inet:gethostname(), 71 | User = <<"guest">>, 72 | Password = <<"guest">>, 73 | %% create a configuration map 74 | OpnConf = #{address => Hostname, 75 | port => Port, 76 | container_id => <<"test-container">>, 77 | sasl => {plain, User, Password}}, 78 | {ok, Connection} = amqp10_client:open_connection(OpnConf), 79 | {ok, Session} = amqp10_client:begin_session(Connection), 80 | SenderLinkName = <<"test-sender">>, 81 | {ok, Sender} = amqp10_client:attach_sender_link(Session, SenderLinkName, <<"a-queue-maybe">>), 82 | 83 | %% wait for credit to be received 84 | receive 85 | {amqp10_event, {link, Sender, credited}} -> ok 86 | after 2000 -> 87 | exit(credited_timeout) 88 | end. 89 | 90 | %% create a new message using a delivery-tag, body and indicate 91 | %% it's settlement status (true meaning no disposition confirmation 92 | %% will be sent by the receiver). 93 | OutMsg = amqp10_msg:new(<<"my-tag">>, <<"my-body">>, true), 94 | ok = amqp10_client:send_msg(Sender, OutMsg), 95 | ok = amqp10_client:detach_link(Sender), 96 | 97 | %% create a receiver link 98 | {ok, Receiver} = amqp10_client:attach_receiver_link(Session, <<"test-receiver">>, <<"a-queue-maybe">>), 99 | 100 | %% grant some credit to the remote sender but don't auto-renew it 101 | ok = amqp10_client:flow_link_credit(Receiver, 5, never), 102 | 103 | %% wait for a delivery 104 | receive 105 | {amqp10_msg, Receiver, InMsg} -> ok 106 | after 2000 -> 107 | exit(delivery_timeout) 108 | end. 109 | 110 | ok = amqp10_client:close_connection(Connection), 111 | ``` 112 | 113 | 114 | ### Events 115 | 116 | The `ampq10_client` API is mostly asynchronous with respect to the AMQP 1.0 117 | protocol. Functions such as `amqp10_client:open_connection` typically return 118 | after the `Open` frame has been successfully written to the socket rather than 119 | waiting until the remote end returns with their `Open` frame. The client will 120 | notify the caller of various internal/async events using `amqp10_event` 121 | messages. In the example above when the remote replies with their `Open` frame 122 | a message is sent of the following forma: 123 | 124 | ``` 125 | {amqp10_event, {connection, ConnectionPid, opened}} 126 | ``` 127 | 128 | When the connection is closed an event is issued as such: 129 | 130 | ``` 131 | {amqp10_event, {connection, ConnectionPid, {closed, Why}}} 132 | ``` 133 | 134 | `Why` could be `normal` or contain a description of an error that occured 135 | and resulted in the closure of the connection. 136 | 137 | Likewise sessions and links have similar events using a similar format. 138 | 139 | ``` 140 | %% success events 141 | {amqp10_event, {connection, ConnectionPid, opened}} 142 | {amqp10_event, {session, SessionPid, begun}} 143 | {amqp10_event, {link, LinkRef, attached}} 144 | ``` 145 | 146 | ``` 147 | %% error events 148 | {amqp10_event, {connection, ConnectionPid, {closed, Why}}} 149 | {amqp10_event, {session, SessionPid, {ended, Why}}} 150 | {amqp10_event, {link, LinkRef, {detached, Why}}} 151 | ``` 152 | 153 | In addition the client may notify the initiator of certain protocol 154 | events such as a receiver running out of credit or credit being available 155 | to a sender. 156 | 157 | ``` 158 | %% no more credit available to sender 159 | {amqp10_event, {link, Sender, credit_exhausted}} 160 | %% sender credit received 161 | {amqp10_event, {link, Sender, credited}} 162 | ``` 163 | 164 | Other events may be declared as necessary, Hence it makes sense for a user 165 | of the client to handle all `{amqp10_event, _}` events to ensure unexpected 166 | messages aren't kept around in the mailbox. 167 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | elvis, 4 | [ 5 | {config, 6 | [#{dirs => ["src"], 7 | filter => "*.erl", 8 | ruleset => erl_files 9 | }, 10 | #{dirs => ["."], 11 | filter => "Makefile", 12 | ruleset => makefiles 13 | }, 14 | #{dirs => ["."], 15 | filter => "rebar.config", 16 | ruleset => rebar_config 17 | }, 18 | #{dirs => ["."], 19 | filter => "elvis.config", 20 | ruleset => elvis_config 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | ]. 27 | -------------------------------------------------------------------------------- /rabbitmq-components.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(.DEFAULT_GOAL),) 2 | # Define default goal to `all` because this file defines some targets 3 | # before the inclusion of erlang.mk leading to the wrong target becoming 4 | # the default. 5 | .DEFAULT_GOAL = all 6 | endif 7 | 8 | # PROJECT_VERSION defaults to: 9 | # 1. the version exported by rabbitmq-server-release; 10 | # 2. the version stored in `git-revisions.txt`, if it exists; 11 | # 3. a version based on git-describe(1), if it is a Git clone; 12 | # 4. 0.0.0 13 | 14 | PROJECT_VERSION := $(RABBITMQ_VERSION) 15 | 16 | ifeq ($(PROJECT_VERSION),) 17 | PROJECT_VERSION := $(shell \ 18 | if test -f git-revisions.txt; then \ 19 | head -n1 git-revisions.txt | \ 20 | awk '{print $$$(words $(PROJECT_DESCRIPTION) version);}'; \ 21 | else \ 22 | (git describe --dirty --abbrev=7 --tags --always --first-parent \ 23 | 2>/dev/null || echo rabbitmq_v0_0_0) | \ 24 | sed -e 's/^rabbitmq_v//' -e 's/^v//' -e 's/_/./g' -e 's/-/+/' \ 25 | -e 's/-/./g'; \ 26 | fi) 27 | endif 28 | 29 | # -------------------------------------------------------------------- 30 | # RabbitMQ components. 31 | # -------------------------------------------------------------------- 32 | 33 | # For RabbitMQ repositories, we want to checkout branches which match 34 | # the parent project. For instance, if the parent project is on a 35 | # release tag, dependencies must be on the same release tag. If the 36 | # parent project is on a topic branch, dependencies must be on the same 37 | # topic branch or fallback to `stable` or `master` whichever was the 38 | # base of the topic branch. 39 | 40 | dep_amqp_client = git_rmq rabbitmq-erlang-client $(current_rmq_ref) $(base_rmq_ref) master 41 | dep_amqp10_client = git_rmq rabbitmq-amqp1.0-client $(current_rmq_ref) $(base_rmq_ref) master 42 | dep_amqp10_common = git_rmq rabbitmq-amqp1.0-common $(current_rmq_ref) $(base_rmq_ref) master 43 | dep_rabbit = git_rmq rabbitmq-server $(current_rmq_ref) $(base_rmq_ref) master 44 | dep_rabbit_common = git_rmq rabbitmq-common $(current_rmq_ref) $(base_rmq_ref) master 45 | dep_rabbitmq_amqp1_0 = git_rmq rabbitmq-amqp1.0 $(current_rmq_ref) $(base_rmq_ref) master 46 | dep_rabbitmq_auth_backend_amqp = git_rmq rabbitmq-auth-backend-amqp $(current_rmq_ref) $(base_rmq_ref) master 47 | dep_rabbitmq_auth_backend_cache = git_rmq rabbitmq-auth-backend-cache $(current_rmq_ref) $(base_rmq_ref) master 48 | dep_rabbitmq_auth_backend_http = git_rmq rabbitmq-auth-backend-http $(current_rmq_ref) $(base_rmq_ref) master 49 | dep_rabbitmq_auth_backend_ldap = git_rmq rabbitmq-auth-backend-ldap $(current_rmq_ref) $(base_rmq_ref) master 50 | dep_rabbitmq_auth_backend_oauth2 = git_rmq rabbitmq-auth-backend-oauth2 $(current_rmq_ref) $(base_rmq_ref) master 51 | dep_rabbitmq_auth_mechanism_ssl = git_rmq rabbitmq-auth-mechanism-ssl $(current_rmq_ref) $(base_rmq_ref) master 52 | dep_rabbitmq_aws = git_rmq rabbitmq-aws $(current_rmq_ref) $(base_rmq_ref) master 53 | dep_rabbitmq_boot_steps_visualiser = git_rmq rabbitmq-boot-steps-visualiser $(current_rmq_ref) $(base_rmq_ref) master 54 | dep_rabbitmq_cli = git_rmq rabbitmq-cli $(current_rmq_ref) $(base_rmq_ref) master 55 | dep_rabbitmq_codegen = git_rmq rabbitmq-codegen $(current_rmq_ref) $(base_rmq_ref) master 56 | dep_rabbitmq_consistent_hash_exchange = git_rmq rabbitmq-consistent-hash-exchange $(current_rmq_ref) $(base_rmq_ref) master 57 | dep_rabbitmq_ct_client_helpers = git_rmq rabbitmq-ct-client-helpers $(current_rmq_ref) $(base_rmq_ref) master 58 | dep_rabbitmq_ct_helpers = git_rmq rabbitmq-ct-helpers $(current_rmq_ref) $(base_rmq_ref) master 59 | dep_rabbitmq_delayed_message_exchange = git_rmq rabbitmq-delayed-message-exchange $(current_rmq_ref) $(base_rmq_ref) master 60 | dep_rabbitmq_dotnet_client = git_rmq rabbitmq-dotnet-client $(current_rmq_ref) $(base_rmq_ref) master 61 | dep_rabbitmq_event_exchange = git_rmq rabbitmq-event-exchange $(current_rmq_ref) $(base_rmq_ref) master 62 | dep_rabbitmq_federation = git_rmq rabbitmq-federation $(current_rmq_ref) $(base_rmq_ref) master 63 | dep_rabbitmq_federation_management = git_rmq rabbitmq-federation-management $(current_rmq_ref) $(base_rmq_ref) master 64 | dep_rabbitmq_java_client = git_rmq rabbitmq-java-client $(current_rmq_ref) $(base_rmq_ref) master 65 | dep_rabbitmq_jms_client = git_rmq rabbitmq-jms-client $(current_rmq_ref) $(base_rmq_ref) master 66 | dep_rabbitmq_jms_cts = git_rmq rabbitmq-jms-cts $(current_rmq_ref) $(base_rmq_ref) master 67 | dep_rabbitmq_jms_topic_exchange = git_rmq rabbitmq-jms-topic-exchange $(current_rmq_ref) $(base_rmq_ref) master 68 | dep_rabbitmq_lvc_exchange = git_rmq rabbitmq-lvc-exchange $(current_rmq_ref) $(base_rmq_ref) master 69 | dep_rabbitmq_management = git_rmq rabbitmq-management $(current_rmq_ref) $(base_rmq_ref) master 70 | dep_rabbitmq_management_agent = git_rmq rabbitmq-management-agent $(current_rmq_ref) $(base_rmq_ref) master 71 | dep_rabbitmq_management_exchange = git_rmq rabbitmq-management-exchange $(current_rmq_ref) $(base_rmq_ref) master 72 | dep_rabbitmq_management_themes = git_rmq rabbitmq-management-themes $(current_rmq_ref) $(base_rmq_ref) master 73 | dep_rabbitmq_message_timestamp = git_rmq rabbitmq-message-timestamp $(current_rmq_ref) $(base_rmq_ref) master 74 | dep_rabbitmq_metronome = git_rmq rabbitmq-metronome $(current_rmq_ref) $(base_rmq_ref) master 75 | dep_rabbitmq_mqtt = git_rmq rabbitmq-mqtt $(current_rmq_ref) $(base_rmq_ref) master 76 | dep_rabbitmq_objc_client = git_rmq rabbitmq-objc-client $(current_rmq_ref) $(base_rmq_ref) master 77 | dep_rabbitmq_peer_discovery_aws = git_rmq rabbitmq-peer-discovery-aws $(current_rmq_ref) $(base_rmq_ref) master 78 | dep_rabbitmq_peer_discovery_common = git_rmq rabbitmq-peer-discovery-common $(current_rmq_ref) $(base_rmq_ref) master 79 | dep_rabbitmq_peer_discovery_consul = git_rmq rabbitmq-peer-discovery-consul $(current_rmq_ref) $(base_rmq_ref) master 80 | dep_rabbitmq_peer_discovery_etcd = git_rmq rabbitmq-peer-discovery-etcd $(current_rmq_ref) $(base_rmq_ref) master 81 | dep_rabbitmq_peer_discovery_k8s = git_rmq rabbitmq-peer-discovery-k8s $(current_rmq_ref) $(base_rmq_ref) master 82 | dep_rabbitmq_prometheus = git_rmq rabbitmq-prometheus $(current_rmq_ref) $(base_rmq_ref) master 83 | dep_rabbitmq_random_exchange = git_rmq rabbitmq-random-exchange $(current_rmq_ref) $(base_rmq_ref) master 84 | dep_rabbitmq_recent_history_exchange = git_rmq rabbitmq-recent-history-exchange $(current_rmq_ref) $(base_rmq_ref) master 85 | dep_rabbitmq_routing_node_stamp = git_rmq rabbitmq-routing-node-stamp $(current_rmq_ref) $(base_rmq_ref) master 86 | dep_rabbitmq_rtopic_exchange = git_rmq rabbitmq-rtopic-exchange $(current_rmq_ref) $(base_rmq_ref) master 87 | dep_rabbitmq_server_release = git_rmq rabbitmq-server-release $(current_rmq_ref) $(base_rmq_ref) master 88 | dep_rabbitmq_sharding = git_rmq rabbitmq-sharding $(current_rmq_ref) $(base_rmq_ref) master 89 | dep_rabbitmq_shovel = git_rmq rabbitmq-shovel $(current_rmq_ref) $(base_rmq_ref) master 90 | dep_rabbitmq_shovel_management = git_rmq rabbitmq-shovel-management $(current_rmq_ref) $(base_rmq_ref) master 91 | dep_rabbitmq_stomp = git_rmq rabbitmq-stomp $(current_rmq_ref) $(base_rmq_ref) master 92 | dep_rabbitmq_stream = git_rmq rabbitmq-stream $(current_rmq_ref) $(base_rmq_ref) master 93 | dep_rabbitmq_toke = git_rmq rabbitmq-toke $(current_rmq_ref) $(base_rmq_ref) master 94 | dep_rabbitmq_top = git_rmq rabbitmq-top $(current_rmq_ref) $(base_rmq_ref) master 95 | dep_rabbitmq_tracing = git_rmq rabbitmq-tracing $(current_rmq_ref) $(base_rmq_ref) master 96 | dep_rabbitmq_trust_store = git_rmq rabbitmq-trust-store $(current_rmq_ref) $(base_rmq_ref) master 97 | dep_rabbitmq_test = git_rmq rabbitmq-test $(current_rmq_ref) $(base_rmq_ref) master 98 | dep_rabbitmq_web_dispatch = git_rmq rabbitmq-web-dispatch $(current_rmq_ref) $(base_rmq_ref) master 99 | dep_rabbitmq_web_stomp = git_rmq rabbitmq-web-stomp $(current_rmq_ref) $(base_rmq_ref) master 100 | dep_rabbitmq_web_stomp_examples = git_rmq rabbitmq-web-stomp-examples $(current_rmq_ref) $(base_rmq_ref) master 101 | dep_rabbitmq_web_mqtt = git_rmq rabbitmq-web-mqtt $(current_rmq_ref) $(base_rmq_ref) master 102 | dep_rabbitmq_web_mqtt_examples = git_rmq rabbitmq-web-mqtt-examples $(current_rmq_ref) $(base_rmq_ref) master 103 | dep_rabbitmq_website = git_rmq rabbitmq-website $(current_rmq_ref) $(base_rmq_ref) live master 104 | dep_toke = git_rmq toke $(current_rmq_ref) $(base_rmq_ref) master 105 | 106 | dep_rabbitmq_public_umbrella = git_rmq rabbitmq-public-umbrella $(current_rmq_ref) $(base_rmq_ref) master 107 | 108 | # Third-party dependencies version pinning. 109 | # 110 | # We do that in this file, which is copied in all projects, to ensure 111 | # all projects use the same versions. It avoids conflicts and makes it 112 | # possible to work with rabbitmq-public-umbrella. 113 | 114 | dep_accept = hex 0.3.5 115 | dep_cowboy = hex 2.8.0 116 | dep_cowlib = hex 2.9.1 117 | dep_jsx = hex 2.11.0 118 | dep_lager = hex 3.8.0 119 | dep_prometheus = git https://github.com/deadtrickster/prometheus.erl.git master 120 | dep_ra = git https://github.com/rabbitmq/ra.git master 121 | dep_ranch = hex 1.7.1 122 | dep_recon = hex 2.5.1 123 | dep_observer_cli = hex 1.5.4 124 | dep_stdout_formatter = hex 0.2.4 125 | dep_sysmon_handler = hex 1.3.0 126 | 127 | RABBITMQ_COMPONENTS = amqp_client \ 128 | amqp10_common \ 129 | amqp10_client \ 130 | rabbit \ 131 | rabbit_common \ 132 | rabbitmq_amqp1_0 \ 133 | rabbitmq_auth_backend_amqp \ 134 | rabbitmq_auth_backend_cache \ 135 | rabbitmq_auth_backend_http \ 136 | rabbitmq_auth_backend_ldap \ 137 | rabbitmq_auth_backend_oauth2 \ 138 | rabbitmq_auth_mechanism_ssl \ 139 | rabbitmq_aws \ 140 | rabbitmq_boot_steps_visualiser \ 141 | rabbitmq_cli \ 142 | rabbitmq_codegen \ 143 | rabbitmq_consistent_hash_exchange \ 144 | rabbitmq_ct_client_helpers \ 145 | rabbitmq_ct_helpers \ 146 | rabbitmq_delayed_message_exchange \ 147 | rabbitmq_dotnet_client \ 148 | rabbitmq_event_exchange \ 149 | rabbitmq_federation \ 150 | rabbitmq_federation_management \ 151 | rabbitmq_java_client \ 152 | rabbitmq_jms_client \ 153 | rabbitmq_jms_cts \ 154 | rabbitmq_jms_topic_exchange \ 155 | rabbitmq_lvc_exchange \ 156 | rabbitmq_management \ 157 | rabbitmq_management_agent \ 158 | rabbitmq_management_exchange \ 159 | rabbitmq_management_themes \ 160 | rabbitmq_message_timestamp \ 161 | rabbitmq_metronome \ 162 | rabbitmq_mqtt \ 163 | rabbitmq_objc_client \ 164 | rabbitmq_peer_discovery_aws \ 165 | rabbitmq_peer_discovery_common \ 166 | rabbitmq_peer_discovery_consul \ 167 | rabbitmq_peer_discovery_etcd \ 168 | rabbitmq_peer_discovery_k8s \ 169 | rabbitmq_prometheus \ 170 | rabbitmq_random_exchange \ 171 | rabbitmq_recent_history_exchange \ 172 | rabbitmq_routing_node_stamp \ 173 | rabbitmq_rtopic_exchange \ 174 | rabbitmq_server_release \ 175 | rabbitmq_sharding \ 176 | rabbitmq_shovel \ 177 | rabbitmq_shovel_management \ 178 | rabbitmq_stomp \ 179 | rabbitmq_stream \ 180 | rabbitmq_toke \ 181 | rabbitmq_top \ 182 | rabbitmq_tracing \ 183 | rabbitmq_trust_store \ 184 | rabbitmq_web_dispatch \ 185 | rabbitmq_web_mqtt \ 186 | rabbitmq_web_mqtt_examples \ 187 | rabbitmq_web_stomp \ 188 | rabbitmq_web_stomp_examples \ 189 | rabbitmq_website 190 | 191 | # Erlang.mk does not rebuild dependencies by default, once they were 192 | # compiled once, except for those listed in the `$(FORCE_REBUILD)` 193 | # variable. 194 | # 195 | # We want all RabbitMQ components to always be rebuilt: this eases 196 | # the work on several components at the same time. 197 | 198 | FORCE_REBUILD = $(RABBITMQ_COMPONENTS) 199 | 200 | # Several components have a custom erlang.mk/build.config, mainly 201 | # to disable eunit. Therefore, we can't use the top-level project's 202 | # erlang.mk copy. 203 | NO_AUTOPATCH += $(RABBITMQ_COMPONENTS) 204 | 205 | ifeq ($(origin current_rmq_ref),undefined) 206 | ifneq ($(wildcard .git),) 207 | current_rmq_ref := $(shell (\ 208 | ref=$$(LANG=C git branch --list | awk '/^\* \(.*detached / {ref=$$0; sub(/.*detached [^ ]+ /, "", ref); sub(/\)$$/, "", ref); print ref; exit;} /^\* / {ref=$$0; sub(/^\* /, "", ref); print ref; exit}');\ 209 | if test "$$(git rev-parse --short HEAD)" != "$$ref"; then echo "$$ref"; fi)) 210 | else 211 | current_rmq_ref := master 212 | endif 213 | endif 214 | export current_rmq_ref 215 | 216 | ifeq ($(origin base_rmq_ref),undefined) 217 | ifneq ($(wildcard .git),) 218 | possible_base_rmq_ref := master 219 | ifeq ($(possible_base_rmq_ref),$(current_rmq_ref)) 220 | base_rmq_ref := $(current_rmq_ref) 221 | else 222 | base_rmq_ref := $(shell \ 223 | (git rev-parse --verify -q master >/dev/null && \ 224 | git rev-parse --verify -q $(possible_base_rmq_ref) >/dev/null && \ 225 | git merge-base --is-ancestor $$(git merge-base master HEAD) $(possible_base_rmq_ref) && \ 226 | echo $(possible_base_rmq_ref)) || \ 227 | echo master) 228 | endif 229 | else 230 | base_rmq_ref := master 231 | endif 232 | endif 233 | export base_rmq_ref 234 | 235 | # Repository URL selection. 236 | # 237 | # First, we infer other components' location from the current project 238 | # repository URL, if it's a Git repository: 239 | # - We take the "origin" remote URL as the base 240 | # - The current project name and repository name is replaced by the 241 | # target's properties: 242 | # eg. rabbitmq-common is replaced by rabbitmq-codegen 243 | # eg. rabbit_common is replaced by rabbitmq_codegen 244 | # 245 | # If cloning from this computed location fails, we fallback to RabbitMQ 246 | # upstream which is GitHub. 247 | 248 | # Macro to transform eg. "rabbit_common" to "rabbitmq-common". 249 | rmq_cmp_repo_name = $(word 2,$(dep_$(1))) 250 | 251 | # Upstream URL for the current project. 252 | RABBITMQ_COMPONENT_REPO_NAME := $(call rmq_cmp_repo_name,$(PROJECT)) 253 | RABBITMQ_UPSTREAM_FETCH_URL ?= https://github.com/rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 254 | RABBITMQ_UPSTREAM_PUSH_URL ?= git@github.com:rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 255 | 256 | # Current URL for the current project. If this is not a Git clone, 257 | # default to the upstream Git repository. 258 | ifneq ($(wildcard .git),) 259 | git_origin_fetch_url := $(shell git config remote.origin.url) 260 | git_origin_push_url := $(shell git config remote.origin.pushurl || git config remote.origin.url) 261 | RABBITMQ_CURRENT_FETCH_URL ?= $(git_origin_fetch_url) 262 | RABBITMQ_CURRENT_PUSH_URL ?= $(git_origin_push_url) 263 | else 264 | RABBITMQ_CURRENT_FETCH_URL ?= $(RABBITMQ_UPSTREAM_FETCH_URL) 265 | RABBITMQ_CURRENT_PUSH_URL ?= $(RABBITMQ_UPSTREAM_PUSH_URL) 266 | endif 267 | 268 | # Macro to replace the following pattern: 269 | # 1. /foo.git -> /bar.git 270 | # 2. /foo -> /bar 271 | # 3. /foo/ -> /bar/ 272 | subst_repo_name = $(patsubst %/$(1)/%,%/$(2)/%,$(patsubst %/$(1),%/$(2),$(patsubst %/$(1).git,%/$(2).git,$(3)))) 273 | 274 | # Macro to replace both the project's name (eg. "rabbit_common") and 275 | # repository name (eg. "rabbitmq-common") by the target's equivalent. 276 | # 277 | # This macro is kept on one line because we don't want whitespaces in 278 | # the returned value, as it's used in $(dep_fetch_git_rmq) in a shell 279 | # single-quoted string. 280 | dep_rmq_repo = $(if $(dep_$(2)),$(call subst_repo_name,$(PROJECT),$(2),$(call subst_repo_name,$(RABBITMQ_COMPONENT_REPO_NAME),$(call rmq_cmp_repo_name,$(2)),$(1))),$(pkg_$(1)_repo)) 281 | 282 | dep_rmq_commits = $(if $(dep_$(1)), \ 283 | $(wordlist 3,$(words $(dep_$(1))),$(dep_$(1))), \ 284 | $(pkg_$(1)_commit)) 285 | 286 | define dep_fetch_git_rmq 287 | fetch_url1='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_FETCH_URL),$(1))'; \ 288 | fetch_url2='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_FETCH_URL),$(1))'; \ 289 | if test "$$$$fetch_url1" != '$(RABBITMQ_CURRENT_FETCH_URL)' && \ 290 | git clone -q -n -- "$$$$fetch_url1" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 291 | fetch_url="$$$$fetch_url1"; \ 292 | push_url='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_PUSH_URL),$(1))'; \ 293 | elif git clone -q -n -- "$$$$fetch_url2" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 294 | fetch_url="$$$$fetch_url2"; \ 295 | push_url='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_PUSH_URL),$(1))'; \ 296 | fi; \ 297 | cd $(DEPS_DIR)/$(call dep_name,$(1)) && ( \ 298 | $(foreach ref,$(call dep_rmq_commits,$(1)), \ 299 | git checkout -q $(ref) >/dev/null 2>&1 || \ 300 | ) \ 301 | (echo "error: no valid pathspec among: $(call dep_rmq_commits,$(1))" \ 302 | 1>&2 && false) ) && \ 303 | (test "$$$$fetch_url" = "$$$$push_url" || \ 304 | git remote set-url --push origin "$$$$push_url") 305 | endef 306 | 307 | # -------------------------------------------------------------------- 308 | # Component distribution. 309 | # -------------------------------------------------------------------- 310 | 311 | list-dist-deps:: 312 | @: 313 | 314 | prepare-dist:: 315 | @: 316 | 317 | # -------------------------------------------------------------------- 318 | # Umbrella-specific settings. 319 | # -------------------------------------------------------------------- 320 | 321 | # If the top-level project is a RabbitMQ component, we override 322 | # $(DEPS_DIR) for this project to point to the top-level's one. 323 | # 324 | # We also verify that the guessed DEPS_DIR is actually named `deps`, 325 | # to rule out any situation where it is a coincidence that we found a 326 | # `rabbitmq-components.mk` up upper directories. 327 | 328 | possible_deps_dir_1 = $(abspath ..) 329 | possible_deps_dir_2 = $(abspath ../../..) 330 | 331 | ifeq ($(notdir $(possible_deps_dir_1)),deps) 332 | ifneq ($(wildcard $(possible_deps_dir_1)/../rabbitmq-components.mk),) 333 | deps_dir_overriden = 1 334 | DEPS_DIR ?= $(possible_deps_dir_1) 335 | DISABLE_DISTCLEAN = 1 336 | endif 337 | endif 338 | 339 | ifeq ($(deps_dir_overriden),) 340 | ifeq ($(notdir $(possible_deps_dir_2)),deps) 341 | ifneq ($(wildcard $(possible_deps_dir_2)/../rabbitmq-components.mk),) 342 | deps_dir_overriden = 1 343 | DEPS_DIR ?= $(possible_deps_dir_2) 344 | DISABLE_DISTCLEAN = 1 345 | endif 346 | endif 347 | endif 348 | 349 | ifneq ($(wildcard UMBRELLA.md),) 350 | DISABLE_DISTCLEAN = 1 351 | endif 352 | 353 | # We disable `make distclean` so $(DEPS_DIR) is not accidentally removed. 354 | 355 | ifeq ($(DISABLE_DISTCLEAN),1) 356 | ifneq ($(filter distclean distclean-deps,$(MAKECMDGOALS)),) 357 | SKIP_DEPS = 1 358 | endif 359 | endif 360 | -------------------------------------------------------------------------------- /src/amqp10_client.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(amqp10_client). 9 | 10 | -include("amqp10_client.hrl"). 11 | -include_lib("amqp10_common/include/amqp10_framing.hrl"). 12 | 13 | -export([open_connection/1, 14 | open_connection/2, 15 | close_connection/1, 16 | begin_session/1, 17 | begin_session_sync/1, 18 | begin_session_sync/2, 19 | end_session/1, 20 | attach_sender_link/3, 21 | attach_sender_link/4, 22 | attach_sender_link/5, 23 | attach_sender_link_sync/3, 24 | attach_sender_link_sync/4, 25 | attach_sender_link_sync/5, 26 | attach_receiver_link/3, 27 | attach_receiver_link/4, 28 | attach_receiver_link/5, 29 | attach_receiver_link/6, 30 | attach_receiver_link/7, 31 | attach_link/2, 32 | detach_link/1, 33 | send_msg/2, 34 | accept_msg/2, 35 | flow_link_credit/3, 36 | flow_link_credit/4, 37 | echo/1, 38 | link_handle/1, 39 | get_msg/1, 40 | get_msg/2, 41 | parse_uri/1 42 | ]). 43 | 44 | -define(DEFAULT_TIMEOUT, 5000). 45 | 46 | -type snd_settle_mode() :: amqp10_client_session:snd_settle_mode(). 47 | -type rcv_settle_mode() :: amqp10_client_session:rcv_settle_mode(). 48 | 49 | -type terminus_durability() :: amqp10_client_session:terminus_durability(). 50 | 51 | -type target_def() :: amqp10_client_session:target_def(). 52 | -type source_def() :: amqp10_client_session:source_def(). 53 | 54 | -type attach_role() :: amqp10_client_session:attach_role(). 55 | -type attach_args() :: amqp10_client_session:attach_args(). 56 | -type filter() :: amqp10_client_session:filter(). 57 | -type properties() :: amqp10_client_session:properties(). 58 | 59 | -type connection_config() :: amqp10_client_connection:connection_config(). 60 | 61 | -opaque link_ref() :: #link_ref{}. 62 | 63 | -export_type([ 64 | link_ref/0, 65 | snd_settle_mode/0, 66 | rcv_settle_mode/0, 67 | terminus_durability/0, 68 | target_def/0, 69 | source_def/0, 70 | attach_role/0, 71 | attach_args/0, 72 | connection_config/0 73 | ]). 74 | 75 | -ifdef (OTP_RELEASE). 76 | -if(?OTP_RELEASE >= 23). 77 | -compile({nowarn_deprecated_function, [{http_uri, decode, 1}]}). 78 | -endif. 79 | -endif. 80 | 81 | %% @doc Convenience function for opening a connection providing only an 82 | %% address and port. This uses anonymous sasl authentication. 83 | %% This is asynchronous and will notify success/closure to the caller using 84 | %% an amqp10_event of the following format: 85 | %% {amqp10_event, {connection, ConnectionPid, opened | {closed, Why}}} 86 | -spec open_connection(inet:socket_address() | inet:hostname(), 87 | inet:port_number()) -> supervisor:startchild_ret(). 88 | open_connection(Addr, Port) -> 89 | open_connection(#{address => Addr, port => Port, notify => self(), 90 | sasl => anon}). 91 | 92 | %% @doc Opens a connection using a connection_config map 93 | %% This is asynchronous and will notify success/closure to the caller using 94 | %% an amqp10_event of the following format: 95 | %% {amqp10_event, {connection, ConnectionPid, opened | {closed, Why}}} 96 | -spec open_connection(connection_config()) -> 97 | supervisor:startchild_ret(). 98 | open_connection(ConnectionConfig0) -> 99 | Notify = maps:get(notify, ConnectionConfig0, self()), 100 | NotifyWhenOpened = maps:get(notify_when_opened, ConnectionConfig0, self()), 101 | NotifyWhenClosed = maps:get(notify_when_closed, ConnectionConfig0, self()), 102 | amqp10_client_connection:open(ConnectionConfig0#{ 103 | notify => Notify, 104 | notify_when_opened => NotifyWhenOpened, 105 | notify_when_closed => NotifyWhenClosed 106 | }). 107 | 108 | %% @doc Opens a connection using a connection_config map 109 | %% This is asynchronous and will notify completion to the caller using 110 | %% an amqp10_event of the following format: 111 | %% {amqp10_event, {connection, ConnectionPid, {closed, Why}}} 112 | -spec close_connection(pid()) -> ok. 113 | close_connection(Pid) -> 114 | amqp10_client_connection:close(Pid, none). 115 | 116 | %% @doc Begins an amqp10 session using 'Connection'. 117 | %% This is asynchronous and will notify success/closure to the caller using 118 | %% an amqp10_event of the following format: 119 | %% {amqp10_event, {session, SessionPid, begun | {ended, Why}}} 120 | -spec begin_session(pid()) -> supervisor:startchild_ret(). 121 | begin_session(Connection) when is_pid(Connection) -> 122 | amqp10_client_connection:begin_session(Connection). 123 | 124 | %% @doc Synchronously begins an amqp10 session using 'Connection'. 125 | %% This is a convenience function that awaits the 'begun' event 126 | %% for the newly created session before returning. 127 | -spec begin_session_sync(pid()) -> 128 | supervisor:startchild_ret() | session_timeout. 129 | begin_session_sync(Connection) when is_pid(Connection) -> 130 | begin_session_sync(Connection, ?DEFAULT_TIMEOUT). 131 | 132 | %% @doc Synchronously begins an amqp10 session using 'Connection'. 133 | %% This is a convenience function that awaits the 'begun' event 134 | %% for the newly created session before returning. 135 | -spec begin_session_sync(pid(), non_neg_integer()) -> 136 | supervisor:startchild_ret() | session_timeout. 137 | begin_session_sync(Connection, Timeout) when is_pid(Connection) -> 138 | case begin_session(Connection) of 139 | {ok, Session} -> 140 | receive 141 | {amqp10_event, {session, Session, begun}} -> 142 | {ok, Session}; 143 | {amqp10_event, {session, Session, {ended, Err}}} -> 144 | {error, Err} 145 | after Timeout -> session_timeout 146 | end; 147 | Ret -> Ret 148 | end. 149 | 150 | %% @doc End an amqp10 session. 151 | %% This is asynchronous and will notify completion of the end request to the 152 | %% caller using an amqp10_event of the following format: 153 | %% {amqp10_event, {session, SessionPid, {ended, Why}}} 154 | -spec end_session(pid()) -> ok. 155 | end_session(Pid) -> 156 | amqp10_client_session:'end'(Pid). 157 | 158 | %% @doc Synchronously attach a link on 'Session'. 159 | %% This is a convenience function that awaits attached event 160 | %% for the link before returning. 161 | attach_sender_link_sync(Session, Name, Target) -> 162 | attach_sender_link_sync(Session, Name, Target, mixed). 163 | 164 | %% @doc Synchronously attach a link on 'Session'. 165 | %% This is a convenience function that awaits attached event 166 | %% for the link before returning. 167 | -spec attach_sender_link_sync(pid(), binary(), binary(), 168 | snd_settle_mode()) -> 169 | {ok, link_ref()} | link_timeout. 170 | attach_sender_link_sync(Session, Name, Target, SettleMode) -> 171 | attach_sender_link_sync(Session, Name, Target, SettleMode, none). 172 | 173 | %% @doc Synchronously attach a link on 'Session'. 174 | %% This is a convenience function that awaits attached event 175 | %% for the link before returning. 176 | -spec attach_sender_link_sync(pid(), binary(), binary(), 177 | snd_settle_mode(), terminus_durability()) -> 178 | {ok, link_ref()} | link_timeout. 179 | attach_sender_link_sync(Session, Name, Target, SettleMode, Durability) -> 180 | {ok, Ref} = attach_sender_link(Session, Name, Target, SettleMode, 181 | Durability), 182 | receive 183 | {amqp10_event, {link, Ref, attached}} -> 184 | {ok, Ref}; 185 | {amqp10_event, {link, Ref, {detached, Err}}} -> 186 | {error, Err} 187 | after ?DEFAULT_TIMEOUT -> link_timeout 188 | end. 189 | 190 | %% @doc Attaches a sender link to a target. 191 | %% This is asynchronous and will notify completion of the attach request to the 192 | %% caller using an amqp10_event of the following format: 193 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 194 | -spec attach_sender_link(pid(), binary(), binary()) -> {ok, link_ref()}. 195 | attach_sender_link(Session, Name, Target) -> 196 | % mixed should work with any type of msg 197 | attach_sender_link(Session, Name, Target, mixed). 198 | 199 | %% @doc Attaches a sender link to a target. 200 | %% This is asynchronous and will notify completion of the attach request to the 201 | %% caller using an amqp10_event of the following format: 202 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 203 | -spec attach_sender_link(pid(), binary(), binary(), 204 | snd_settle_mode()) -> 205 | {ok, link_ref()}. 206 | attach_sender_link(Session, Name, Target, SettleMode) -> 207 | attach_sender_link(Session, Name, Target, SettleMode, none). 208 | 209 | %% @doc Attaches a sender link to a target. 210 | %% This is asynchronous and will notify completion of the attach request to the 211 | %% caller using an amqp10_event of the following format: 212 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 213 | -spec attach_sender_link(pid(), binary(), binary(), 214 | snd_settle_mode(), terminus_durability()) -> 215 | {ok, link_ref()}. 216 | attach_sender_link(Session, Name, Target, SettleMode, Durability) -> 217 | AttachArgs = #{name => Name, 218 | role => {sender, #{address => Target, 219 | durable => Durability}}, 220 | snd_settle_mode => SettleMode, 221 | rcv_settle_mode => first}, 222 | amqp10_client_session:attach(Session, AttachArgs). 223 | 224 | %% @doc Attaches a receiver link to a source. 225 | %% This is asynchronous and will notify completion of the attach request to the 226 | %% caller using an amqp10_event of the following format: 227 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 228 | -spec attach_receiver_link(pid(), binary(), binary()) -> 229 | {ok, link_ref()}. 230 | attach_receiver_link(Session, Name, Source) -> 231 | attach_receiver_link(Session, Name, Source, settled). 232 | 233 | %% @doc Attaches a receiver link to a source. 234 | %% This is asynchronous and will notify completion of the attach request to the 235 | %% caller using an amqp10_event of the following format: 236 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 237 | -spec attach_receiver_link(pid(), binary(), binary(), 238 | snd_settle_mode()) -> 239 | {ok, link_ref()}. 240 | attach_receiver_link(Session, Name, Source, SettleMode) -> 241 | attach_receiver_link(Session, Name, Source, SettleMode, none). 242 | 243 | %% @doc Attaches a receiver link to a source. 244 | %% This is asynchronous and will notify completion of the attach request to the 245 | %% caller using an amqp10_event of the following format: 246 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 247 | -spec attach_receiver_link(pid(), binary(), binary(), 248 | snd_settle_mode(), terminus_durability()) -> 249 | {ok, link_ref()}. 250 | attach_receiver_link(Session, Name, Source, SettleMode, Durability) -> 251 | attach_receiver_link(Session, Name, Source, SettleMode, Durability, #{}). 252 | 253 | %% @doc Attaches a receiver link to a source. 254 | %% This is asynchronous and will notify completion of the attach request to the 255 | %% caller using an amqp10_event of the following format: 256 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 257 | -spec attach_receiver_link(pid(), binary(), binary(), 258 | snd_settle_mode(), terminus_durability(), filter()) -> 259 | {ok, link_ref()}. 260 | attach_receiver_link(Session, Name, Source, SettleMode, Durability, Filter) -> 261 | attach_receiver_link(Session, Name, Source, SettleMode, Durability, Filter, #{}). 262 | 263 | %% @doc Attaches a receiver link to a source. 264 | %% This is asynchronous and will notify completion of the attach request to the 265 | %% caller using an amqp10_event of the following format: 266 | %% {amqp10_event, {link, LinkRef, attached | {detached, Why}}} 267 | -spec attach_receiver_link(pid(), binary(), binary(), 268 | snd_settle_mode(), terminus_durability(), filter(), 269 | properties()) -> 270 | {ok, link_ref()}. 271 | attach_receiver_link(Session, Name, Source, SettleMode, Durability, Filter, Properties) -> 272 | AttachArgs = #{name => Name, 273 | role => {receiver, #{address => Source, 274 | durable => Durability}, self()}, 275 | snd_settle_mode => SettleMode, 276 | rcv_settle_mode => first, 277 | filter => Filter, 278 | properties => Properties}, 279 | amqp10_client_session:attach(Session, AttachArgs). 280 | 281 | -spec attach_link(pid(), attach_args()) -> {ok, link_ref()}. 282 | attach_link(Session, AttachArgs) -> 283 | amqp10_client_session:attach(Session, AttachArgs). 284 | 285 | %% @doc Detaches a link. 286 | %% This is asynchronous and will notify completion of the attach request to the 287 | %% caller using an amqp10_event of the following format: 288 | %% {amqp10_event, {link, LinkRef, {detached, Why}}} 289 | -spec detach_link(link_ref()) -> _. 290 | detach_link(#link_ref{link_handle = Handle, session = Session}) -> 291 | amqp10_client_session:detach(Session, Handle). 292 | 293 | %% @doc Grant credit to a sender. 294 | %% The amqp10_client will automatically grant more credit to the sender when 295 | %% the remaining link credit falls below the value of RenewWhenBelow. 296 | %% If RenewWhenBelow is 'never' the client will never grant new credit. Instead 297 | %% the caller will be notified when the link_credit reaches 0 with an 298 | %% amqp10_event of the following format: 299 | %% {amqp10_event, {link, LinkRef, credit_exhausted}} 300 | -spec flow_link_credit(link_ref(), Credit :: non_neg_integer(), 301 | RenewWhenBelow :: never | non_neg_integer()) -> ok. 302 | flow_link_credit(Ref, Credit, RenewWhenBelow) -> 303 | flow_link_credit(Ref, Credit, RenewWhenBelow, false). 304 | 305 | -spec flow_link_credit(link_ref(), Credit :: non_neg_integer(), 306 | RenewWhenBelow :: never | non_neg_integer(), 307 | Drain :: boolean()) -> ok. 308 | flow_link_credit(#link_ref{role = receiver, session = Session, 309 | link_handle = Handle}, 310 | Credit, RenewWhenBelow, Drain) -> 311 | Flow = #'v1_0.flow'{link_credit = {uint, Credit}, 312 | drain = Drain}, 313 | ok = amqp10_client_session:flow(Session, Handle, Flow, RenewWhenBelow). 314 | 315 | %% @doc Request that the sender's flow state is echoed back 316 | %% This may be used to determine when the Link has finally quiesced. 317 | %% see §2.6.10 of the spec 318 | echo(#link_ref{role = receiver, session = Session, 319 | link_handle = Handle}) -> 320 | Flow = #'v1_0.flow'{link_credit = {uint, 0}, 321 | echo = true}, 322 | ok = amqp10_client_session:flow(Session, Handle, Flow, 0). 323 | 324 | %%% messages 325 | 326 | %% @doc Send a message on a the link referred to be the 'LinkRef'. 327 | %% Returns ok for "async" transfers when messages are sent with settled=true 328 | %% else it returns the delivery state from the disposition 329 | -spec send_msg(link_ref(), amqp10_msg:amqp10_msg()) -> 330 | ok | {error, insufficient_credit | link_not_found | half_attached}. 331 | send_msg(#link_ref{role = sender, session = Session, 332 | link_handle = Handle}, Msg0) -> 333 | Msg = amqp10_msg:set_handle(Handle, Msg0), 334 | amqp10_client_session:transfer(Session, Msg, ?DEFAULT_TIMEOUT). 335 | 336 | %% @doc Accept a message on a the link referred to be the 'LinkRef'. 337 | -spec accept_msg(link_ref(), amqp10_msg:amqp10_msg()) -> ok. 338 | accept_msg(#link_ref{role = receiver, session = Session}, Msg) -> 339 | DeliveryId = amqp10_msg:delivery_id(Msg), 340 | amqp10_client_session:disposition(Session, receiver, DeliveryId, 341 | DeliveryId, true, accepted). 342 | 343 | %% @doc Get a single message from a link. 344 | %% Flows a single link credit then awaits delivery or timeout. 345 | -spec get_msg(link_ref()) -> {ok, amqp10_msg:amqp10_msg()} | {error, timeout}. 346 | get_msg(LinkRef) -> 347 | get_msg(LinkRef, ?DEFAULT_TIMEOUT). 348 | 349 | %% @doc Get a single message from a link. 350 | %% Flows a single link credit then awaits delivery or timeout. 351 | -spec get_msg(link_ref(), non_neg_integer()) -> 352 | {ok, amqp10_msg:amqp10_msg()} | {error, timeout}. 353 | get_msg(#link_ref{role = receiver} = Ref, Timeout) -> 354 | %flow 1 355 | ok = flow_link_credit(Ref, 1, never), 356 | % wait for transfer 357 | receive 358 | {amqp10_msg, Ref, Message} -> {ok, Message} 359 | after Timeout -> 360 | {error, timeout} 361 | end. 362 | 363 | %% @doc Get the link handle from a LinkRef 364 | -spec link_handle(link_ref()) -> non_neg_integer(). 365 | link_handle(#link_ref{link_handle = Handle}) -> Handle. 366 | 367 | 368 | -spec parse_uri(string()) -> 369 | {ok, connection_config()} | {error, term()}. 370 | parse_uri(Uri) -> 371 | case uri_string:parse(Uri) of 372 | Map when is_map(Map) -> 373 | try 374 | {ok, parse_result(Map)} 375 | catch 376 | throw:Err -> {error, Err} 377 | end; 378 | {error, _, _} = Err -> Err 379 | end. 380 | 381 | parse_result(Map) -> 382 | _ = case maps:get(path, Map, "/") of 383 | "/" -> ok; 384 | "" -> ok; 385 | _ -> throw(path_segment_not_supported) 386 | end, 387 | Scheme = maps:get(scheme, Map, "amqp"), 388 | UserInfo = maps:get(userinfo, Map, undefined), 389 | Host = maps:get(host, Map), 390 | DefaultPort = case Scheme of 391 | "amqp" -> 5672; 392 | "amqps" -> 5671 393 | end, 394 | Port = maps:get(port, Map, DefaultPort), 395 | Query0 = maps:get(query, Map, ""), 396 | Query = maps:from_list(uri_string:dissect_query(Query0)), 397 | Sasl = case Query of 398 | #{"sasl" := "anon"} -> anon; 399 | #{"sasl" := "plain"} when UserInfo =:= undefined orelse length(UserInfo) =:= 0 -> 400 | throw(plain_sasl_missing_userinfo); 401 | _ -> 402 | case UserInfo of 403 | [] -> none; 404 | undefined -> none; 405 | U -> parse_usertoken(U) 406 | end 407 | end, 408 | Ret0 = maps:fold(fun("idle_time_out", V, Acc) -> 409 | Acc#{idle_time_out => list_to_integer(V)}; 410 | ("max_frame_size", V, Acc) -> 411 | Acc#{max_frame_size => list_to_integer(V)}; 412 | ("hostname", V, Acc) -> 413 | Acc#{hostname => list_to_binary(V)}; 414 | ("container_id", V, Acc) -> 415 | Acc#{container_id => list_to_binary(V)}; 416 | ("transfer_limit_margin", V, Acc) -> 417 | Acc#{transfer_limit_margin => list_to_integer(V)}; 418 | (_, _, Acc) -> Acc 419 | end, #{address => Host, 420 | hostname => to_binary(Host), 421 | port => Port, 422 | sasl => Sasl}, Query), 423 | case Scheme of 424 | "amqp" -> Ret0; 425 | "amqps" -> 426 | TlsOpts = parse_tls_opts(Query), 427 | Ret0#{tls_opts => {secure_port, TlsOpts}} 428 | end. 429 | 430 | 431 | parse_usertoken(undefined) -> 432 | none; 433 | parse_usertoken("") -> 434 | none; 435 | parse_usertoken(U) -> 436 | [User, Pass] = string:tokens(U, ":"), 437 | {plain, to_binary(http_uri:decode(User)), to_binary(http_uri:decode(Pass))}. 438 | 439 | parse_tls_opts(M) -> 440 | lists:sort(maps:fold(fun parse_tls_opt/3, [], M)). 441 | 442 | parse_tls_opt(K, V, Acc) 443 | when K =:= "cacertfile" orelse 444 | K =:= "certfile" orelse 445 | K =:= "keyfile" -> 446 | [{to_atom(K), V} | Acc]; 447 | parse_tls_opt("verify", V, Acc) 448 | when V =:= "verify_none" orelse 449 | V =:= "verify_peer" -> 450 | [{verify, to_atom(V)} | Acc]; 451 | parse_tls_opt("verify", _V, _Acc) -> 452 | throw({invalid_option, verify}); 453 | parse_tls_opt("versions", V, Acc) -> 454 | Parts = string:tokens(V, ","), 455 | Versions = [try_to_existing_atom(P) || P <- Parts], 456 | [{versions, Versions} | Acc]; 457 | parse_tls_opt("fail_if_no_peer_cert", V, Acc) 458 | when V =:= "true" orelse 459 | V =:= "false" -> 460 | [{fail_if_no_peer_cert, to_atom(V)} | Acc]; 461 | parse_tls_opt("fail_if_no_peer_cert", _V, _Acc) -> 462 | throw({invalid_option, fail_if_no_peer_cert}); 463 | parse_tls_opt("server_name_indication", "disable", Acc) -> 464 | [{server_name_indication, disable} | Acc]; 465 | parse_tls_opt("server_name_indication", V, Acc) -> 466 | [{server_name_indication, V} | Acc]; 467 | parse_tls_opt(_K, _V, Acc) -> 468 | Acc. 469 | 470 | to_atom(X) when is_list(X) -> list_to_atom(X). 471 | to_binary(X) when is_list(X) -> list_to_binary(X). 472 | 473 | try_to_existing_atom(L) when is_list(L) -> 474 | try list_to_existing_atom(L) of 475 | A -> A 476 | catch 477 | _:badarg -> 478 | throw({non_existent_atom, L}) 479 | end. 480 | 481 | 482 | -ifdef(TEST). 483 | -include_lib("eunit/include/eunit.hrl"). 484 | 485 | parse_uri_test_() -> 486 | [?_assertEqual({ok, #{address => "my_host", 487 | port => 9876, 488 | hostname => <<"my_host">>, 489 | sasl => none}}, parse_uri("amqp://my_host:9876")), 490 | %% port defaults 491 | ?_assertMatch({ok, #{port := 5671}}, parse_uri("amqps://my_host")), 492 | ?_assertMatch({ok, #{port := 5672}}, parse_uri("amqp://my_host")), 493 | ?_assertEqual({ok, #{address => "my_proxy", 494 | port => 9876, 495 | hostname => <<"my_host">>, 496 | container_id => <<"my_container">>, 497 | idle_time_out => 60000, 498 | max_frame_size => 512, 499 | tls_opts => {secure_port, []}, 500 | sasl => {plain, <<"fred">>, <<"passw">>}}}, 501 | parse_uri("amqps://fred:passw@my_proxy:9876?sasl=plain&" 502 | "hostname=my_host&container_id=my_container&" 503 | "max_frame_size=512&idle_time_out=60000")), 504 | %% ensure URI encoded usernames and passwords are decodeded 505 | ?_assertEqual({ok, #{address => "my_proxy", 506 | port => 9876, 507 | hostname => <<"my_proxy">>, 508 | sasl => {plain, <<"fr/ed">>, <<"pa/ssw">>}}}, 509 | parse_uri("amqp://fr%2Fed:pa%2Fssw@my_proxy:9876")), 510 | %% make sasl plain implicit when username and password is present 511 | ?_assertEqual({ok, #{address => "my_proxy", 512 | port => 9876, 513 | hostname => <<"my_proxy">>, 514 | sasl => {plain, <<"fred">>, <<"passw">>}}}, 515 | parse_uri("amqp://fred:passw@my_proxy:9876")), 516 | ?_assertEqual( 517 | {ok, #{address => "my_proxy", port => 9876, 518 | hostname => <<"my_proxy">>, 519 | tls_opts => {secure_port, [{cacertfile, "/etc/cacertfile.pem"}, 520 | {certfile, "/etc/certfile.pem"}, 521 | {fail_if_no_peer_cert, true}, 522 | {keyfile, "/etc/keyfile.key"}, 523 | {server_name_indication, "frazzle"}, 524 | {verify, verify_none}, 525 | {versions, ['tlsv1.1', 'tlsv1.2']} 526 | ]}, 527 | sasl => {plain, <<"fred">>, <<"passw">>}}}, 528 | parse_uri("amqps://fred:passw@my_proxy:9876?sasl=plain" 529 | "&cacertfile=/etc/cacertfile.pem&certfile=/etc/certfile.pem" 530 | "&keyfile=/etc/keyfile.key&verify=verify_none" 531 | "&fail_if_no_peer_cert=true" 532 | "&server_name_indication=frazzle" 533 | "&versions=tlsv1.1,tlsv1.2")), 534 | %% invalid tls version 535 | ?_assertEqual({error, {non_existent_atom, "tlsv1.9999999"}}, 536 | parse_uri("amqps://fred:passw@my_proxy:9876?sasl=plain&" ++ 537 | "versions=tlsv1.1,tlsv1.9999999")), 538 | ?_assertEqual( 539 | {ok, #{address => "my_proxy", 540 | port => 9876, 541 | hostname => <<"my_proxy">>, 542 | tls_opts => {secure_port, [{server_name_indication, disable}]}, 543 | sasl => {plain, <<"fred">>, <<"passw">>}}}, 544 | parse_uri("amqps://fred:passw@my_proxy:9876?sasl=plain" 545 | "&server_name_indication=disable")), 546 | ?_assertEqual({error, {invalid_option, verify}}, 547 | parse_uri("amqps://fred:passw@my_proxy:9876?sasl=plain&" ++ 548 | "cacertfile=/etc/cacertfile.pem&certfile=/etc/certfile.pem&" ++ 549 | "keyfile=/etc/keyfile.key&verify=verify_bananas")), 550 | ?_assertEqual({error, {invalid_option, fail_if_no_peer_cert}}, 551 | parse_uri("amqps://fred:passw@my_proxy:9876?sasl=plain&" ++ 552 | "cacertfile=/etc/cacertfile.pem&certfile=/etc/certfile.pem&" ++ 553 | "keyfile=/etc/keyfile.key&fail_if_no_peer_cert=banana")), 554 | ?_assertEqual({error, plain_sasl_missing_userinfo}, 555 | parse_uri("amqp://my_host:9876?sasl=plain")), 556 | ?_assertEqual({error, path_segment_not_supported}, 557 | parse_uri("amqp://my_host/my_path_segment:9876")) 558 | ]. 559 | 560 | -endif. 561 | -------------------------------------------------------------------------------- /src/amqp10_client.hrl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -define(AMQP_PROTOCOL_HEADER, <<"AMQP", 0, 1, 0, 0>>). 9 | -define(SASL_PROTOCOL_HEADER, <<"AMQP", 3, 1, 0, 0>>). 10 | -define(MIN_MAX_FRAME_SIZE, 512). 11 | -define(MAX_MAX_FRAME_SIZE, 1024 * 1024). 12 | -define(FRAME_HEADER_SIZE, 8). 13 | 14 | -define(TIMEOUT, 5000). 15 | 16 | % -define(debug, true). 17 | -ifdef(debug). 18 | -define(DBG(F, A), error_logger:info_msg(F, A)). 19 | -else. 20 | -define(DBG(F, A), ok). 21 | -endif. 22 | 23 | -record(link_ref, {role :: sender | receiver, session :: pid(), 24 | link_handle :: non_neg_integer()}). 25 | -------------------------------------------------------------------------------- /src/amqp10_client_app.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(amqp10_client_app). 9 | 10 | -behaviour(application). 11 | 12 | %% Application callbacks 13 | -export([start/2, 14 | stop/1]). 15 | 16 | -type start_type() :: ( 17 | normal | 18 | {takeover, Node :: node()} | 19 | {failover, Node :: node()} 20 | ). 21 | -type state() :: term(). 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | -spec start(StartType :: start_type(), StartArgs :: term()) -> 28 | {ok, Pid :: pid()} | {ok, Pid :: pid(), State :: state()} | {error, Reason :: term()}. 29 | start(_Type, _Args) -> 30 | amqp10_client_sup:start_link(). 31 | 32 | -spec stop(State :: state()) -> ok. 33 | stop(_State) -> 34 | ok. 35 | 36 | %%==================================================================== 37 | %% Internal functions 38 | %%==================================================================== 39 | -------------------------------------------------------------------------------- /src/amqp10_client_connection.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(amqp10_client_connection). 9 | 10 | -behaviour(gen_statem). 11 | 12 | -include("amqp10_client.hrl"). 13 | -include_lib("amqp10_common/include/amqp10_framing.hrl"). 14 | 15 | -ifdef(nowarn_deprecated_gen_fsm). 16 | -compile({nowarn_deprecated_function, 17 | [{gen_fsm, reply, 2}, 18 | {gen_fsm, send_all_state_event, 2}, 19 | {gen_fsm, send_event, 2}, 20 | {gen_fsm, start_link, 3}, 21 | {gen_fsm, sync_send_all_state_event, 2}]}). 22 | -endif. 23 | 24 | %% Public API. 25 | -export([open/1, 26 | close/2]). 27 | 28 | %% Private API. 29 | -export([start_link/2, 30 | socket_ready/2, 31 | protocol_header_received/5, 32 | begin_session/1, 33 | heartbeat/1]). 34 | 35 | %% gen_fsm callbacks. 36 | -export([init/1, 37 | callback_mode/0, 38 | terminate/3, 39 | code_change/4]). 40 | 41 | %% gen_fsm state callbacks. 42 | -export([expecting_socket/3, 43 | sasl_hdr_sent/3, 44 | sasl_hdr_rcvds/3, 45 | sasl_init_sent/3, 46 | hdr_sent/3, 47 | open_sent/3, 48 | opened/3, 49 | close_sent/3]). 50 | 51 | -type amqp10_socket() :: {tcp, gen_tcp:socket()} | {ssl, ssl:sslsocket()}. 52 | 53 | -type milliseconds() :: non_neg_integer(). 54 | 55 | -type address() :: inet:socket_address() | inet:hostname(). 56 | 57 | -type connection_config() :: 58 | #{container_id => binary(), % AMQP container id 59 | hostname => binary(), % the dns name of the target host 60 | addresses => [address()], 61 | address => address(), 62 | port => inet:port_number(), 63 | tls_opts => {secure_port, [ssl:ssl_option()]}, 64 | notify => pid() | none, % the pid to send connection events to 65 | notify_when_opened => pid() | none, 66 | notify_when_closed => pid() | none, 67 | max_frame_size => non_neg_integer(), % TODO: constrain to large than 512 68 | outgoing_max_frame_size => non_neg_integer() | undefined, 69 | idle_time_out => milliseconds(), 70 | % set to a negative value to allow a sender to "overshoot" the flow 71 | % control by this margin 72 | transfer_limit_margin => 0 | neg_integer(), 73 | sasl => none | anon | {plain, User :: binary(), Pwd :: binary()}, 74 | notify => pid(), 75 | notify_when_opened => pid() | none, 76 | notify_when_closed => pid() | none 77 | }. 78 | 79 | -record(state, 80 | {next_channel = 1 :: pos_integer(), 81 | connection_sup :: pid(), 82 | reader_m_ref :: reference() | undefined, 83 | sessions_sup :: pid() | undefined, 84 | pending_session_reqs = [] :: [term()], 85 | reader :: pid() | undefined, 86 | socket :: amqp10_socket() | undefined, 87 | idle_time_out :: non_neg_integer() | undefined, 88 | heartbeat_timer :: timer:tref() | undefined, 89 | config :: connection_config() 90 | }). 91 | 92 | -export_type([connection_config/0, 93 | amqp10_socket/0]). 94 | 95 | -define(DEFAULT_TIMEOUT, 5000). 96 | 97 | %% ------------------------------------------------------------------- 98 | %% Public API. 99 | %% ------------------------------------------------------------------- 100 | 101 | -spec open(connection_config()) -> supervisor:startchild_ret(). 102 | open(Config) -> 103 | %% Start the supervision tree dedicated to that connection. It 104 | %% starts at least a connection process (the PID we want to return) 105 | %% and a reader process (responsible for opening and reading the 106 | %% socket). 107 | case supervisor:start_child(amqp10_client_sup, [Config]) of 108 | {ok, ConnSup} -> 109 | %% We query the PIDs of the connection and reader processes. The 110 | %% reader process needs to know the connection PID to send it the 111 | %% socket. 112 | Children = supervisor:which_children(ConnSup), 113 | {_, Reader, _, _} = lists:keyfind(reader, 1, Children), 114 | {_, Connection, _, _} = lists:keyfind(connection, 1, Children), 115 | {_, SessionsSup, _, _} = lists:keyfind(sessions, 1, Children), 116 | set_other_procs(Connection, #{sessions_sup => SessionsSup, 117 | reader => Reader}), 118 | {ok, Connection}; 119 | Error -> 120 | Error 121 | end. 122 | 123 | -spec close(pid(), {amqp10_client_types:amqp_error() 124 | | amqp10_client_types:connection_error(), binary()} | none) -> ok. 125 | close(Pid, Reason) -> 126 | gen_statem:cast(Pid, {close, Reason}). 127 | 128 | %% ------------------------------------------------------------------- 129 | %% Private API. 130 | %% ------------------------------------------------------------------- 131 | 132 | start_link(Sup, Config) -> 133 | gen_statem:start_link(?MODULE, [Sup, Config], []). 134 | 135 | set_other_procs(Pid, OtherProcs) -> 136 | gen_statem:cast(Pid, {set_other_procs, OtherProcs}). 137 | 138 | -spec socket_ready(pid(), amqp10_socket()) -> ok. 139 | socket_ready(Pid, Socket) -> 140 | gen_statem:cast(Pid, {socket_ready, Socket}). 141 | 142 | -spec protocol_header_received(pid(), 0 | 3, non_neg_integer(), 143 | non_neg_integer(), non_neg_integer()) -> ok. 144 | protocol_header_received(Pid, Protocol, Maj, Min, Rev) -> 145 | gen_statem:cast(Pid, {protocol_header_received, Protocol, Maj, Min, Rev}). 146 | 147 | -spec begin_session(pid()) -> supervisor:startchild_ret(). 148 | begin_session(Pid) -> 149 | gen_statem:call(Pid, begin_session, {dirty_timeout, ?TIMEOUT}). 150 | 151 | heartbeat(Pid) -> 152 | gen_statem:cast(Pid, heartbeat). 153 | 154 | %% ------------------------------------------------------------------- 155 | %% gen_fsm callbacks. 156 | %% ------------------------------------------------------------------- 157 | 158 | callback_mode() -> [state_functions]. 159 | 160 | init([Sup, Config0]) -> 161 | process_flag(trap_exit, true), 162 | Config = maps:merge(config_defaults(), Config0), 163 | {ok, expecting_socket, #state{connection_sup = Sup, 164 | config = Config}}. 165 | 166 | expecting_socket(_EvtType, {socket_ready, Socket}, 167 | State = #state{config = Cfg}) -> 168 | State1 = State#state{socket = Socket}, 169 | case Cfg of 170 | #{sasl := none} -> 171 | ok = socket_send(Socket, ?AMQP_PROTOCOL_HEADER), 172 | {next_state, hdr_sent, State1}; 173 | _ -> 174 | ok = socket_send(Socket, ?SASL_PROTOCOL_HEADER), 175 | {next_state, sasl_hdr_sent, State1} 176 | end; 177 | expecting_socket(_EvtType, {set_other_procs, OtherProcs}, State) -> 178 | {keep_state, set_other_procs0(OtherProcs, State)}; 179 | expecting_socket({call, From}, begin_session, 180 | #state{pending_session_reqs = PendingSessionReqs} = State) -> 181 | %% The caller already asked for a new session but the connection 182 | %% isn't fully opened. Let's queue this request until the connection 183 | %% is ready. 184 | State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]}, 185 | {keep_state, State1}. 186 | 187 | sasl_hdr_sent(_EvtType, {protocol_header_received, 3, 1, 0, 0}, State) -> 188 | {next_state, sasl_hdr_rcvds, State}; 189 | sasl_hdr_sent({call, From}, begin_session, 190 | #state{pending_session_reqs = PendingSessionReqs} = State) -> 191 | State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]}, 192 | {keep_state, State1}. 193 | 194 | sasl_hdr_rcvds(_EvtType, #'v1_0.sasl_mechanisms'{ 195 | sasl_server_mechanisms = {array, symbol, Mechs}}, 196 | State = #state{config = #{sasl := Sasl}}) -> 197 | SaslBin = {symbol, sasl_to_bin(Sasl)}, 198 | case lists:any(fun(S) when S =:= SaslBin -> true; 199 | (_) -> false 200 | end, Mechs) of 201 | true -> 202 | ok = send_sasl_init(State, Sasl), 203 | {next_state, sasl_init_sent, State}; 204 | false -> 205 | {stop, {sasl_not_supported, Sasl}, State} 206 | end; 207 | sasl_hdr_rcvds({call, From}, begin_session, 208 | #state{pending_session_reqs = PendingSessionReqs} = State) -> 209 | State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]}, 210 | {keep_state, State1}. 211 | 212 | sasl_init_sent(_EvtType, #'v1_0.sasl_outcome'{code = {ubyte, 0}}, 213 | #state{socket = Socket} = State) -> 214 | ok = socket_send(Socket, ?AMQP_PROTOCOL_HEADER), 215 | {next_state, hdr_sent, State}; 216 | sasl_init_sent(_EvtType, #'v1_0.sasl_outcome'{code = {ubyte, C}}, 217 | #state{} = State) when C==1;C==2;C==3;C==4 -> 218 | {stop, sasl_auth_failure, State}; 219 | sasl_init_sent({call, From}, begin_session, 220 | #state{pending_session_reqs = PendingSessionReqs} = State) -> 221 | State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]}, 222 | {keep_state, State1}. 223 | 224 | hdr_sent(_EvtType, {protocol_header_received, 0, 1, 0, 0}, State) -> 225 | case send_open(State) of 226 | ok -> {next_state, open_sent, State}; 227 | Error -> {stop, Error, State} 228 | end; 229 | hdr_sent(_EvtType, {protocol_header_received, Protocol, Maj, Min, 230 | Rev}, State) -> 231 | error_logger:warning_msg("Unsupported protocol version: ~b ~b.~b.~b~n", 232 | [Protocol, Maj, Min, Rev]), 233 | {stop, normal, State}; 234 | hdr_sent({call, From}, begin_session, 235 | #state{pending_session_reqs = PendingSessionReqs} = State) -> 236 | State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]}, 237 | {keep_state, State1}. 238 | 239 | open_sent(_EvtType, #'v1_0.open'{max_frame_size = MFSz, 240 | idle_time_out = Timeout}, 241 | #state{pending_session_reqs = PendingSessionReqs, 242 | config = Config} = State0) -> 243 | State = case Timeout of 244 | undefined -> State0; 245 | {uint, T} when T > 0 -> 246 | {ok, Tmr} = start_heartbeat_timer(T div 2), 247 | State0#state{idle_time_out = T div 2, 248 | heartbeat_timer = Tmr}; 249 | _ -> State0 250 | end, 251 | State1 = State#state{config = 252 | Config#{outgoing_max_frame_size => unpack(MFSz)}}, 253 | State2 = lists:foldr( 254 | fun(From, S0) -> 255 | {Ret, S2} = handle_begin_session(From, S0), 256 | _ = gen_statem:reply(From, Ret), 257 | S2 258 | end, State1, PendingSessionReqs), 259 | ok = notify_opened(Config), 260 | {next_state, opened, State2}; 261 | open_sent({call, From}, begin_session, 262 | #state{pending_session_reqs = PendingSessionReqs} = State) -> 263 | State1 = State#state{pending_session_reqs = [From | PendingSessionReqs]}, 264 | {keep_state, State1}. 265 | 266 | opened(_EvtType, heartbeat, State = #state{idle_time_out = T}) -> 267 | ok = send_heartbeat(State), 268 | {ok, Tmr} = start_heartbeat_timer(T), 269 | {keep_state, State#state{heartbeat_timer = Tmr}}; 270 | opened(_EvtType, {close, Reason}, State = #state{config = Config}) -> 271 | %% We send the first close frame and wait for the reply. 272 | %% TODO: stop all sessions writing 273 | %% We could still accept incoming frames (See: 2.4.6) 274 | ok = notify_closed(Config, Reason), 275 | case send_close(State, Reason) of 276 | ok -> {next_state, close_sent, State}; 277 | {error, closed} -> {stop, normal, State}; 278 | Error -> {stop, Error, State} 279 | end; 280 | opened(_EvtType, #'v1_0.close'{error = Error}, State = #state{config = Config}) -> 281 | %% We receive the first close frame, reply and terminate. 282 | ok = notify_closed(Config, translate_err(Error)), 283 | _ = send_close(State, none), 284 | {stop, normal, State}; 285 | opened({call, From}, begin_session, State) -> 286 | {Ret, State1} = handle_begin_session(From, State), 287 | {keep_state, State1, [{reply, From, Ret}]}; 288 | opened(info, {'DOWN', MRef, _, _, _Info}, 289 | State = #state{reader_m_ref = MRef, config = Config}) -> 290 | %% reader has gone down and we are not already shutting down 291 | ok = notify_closed(Config, shutdown), 292 | {stop, normal, State}; 293 | opened(_EvtType, Frame, State) -> 294 | error_logger:warning_msg("Unexpected connection frame ~p when in state ~p ~n", 295 | [Frame, State]), 296 | {keep_state, State}. 297 | 298 | close_sent(_EvtType, heartbeat, State) -> 299 | {next_state, close_sent, State}; 300 | close_sent(_EvtType, #'v1_0.close'{}, State) -> 301 | %% TODO: we should probably set up a timer before this to ensure 302 | %% we close down event if no reply is received 303 | {stop, normal, State}. 304 | 305 | set_other_procs0(OtherProcs, State) -> 306 | #{sessions_sup := SessionsSup, 307 | reader := Reader} = OtherProcs, 308 | ReaderMRef = monitor(process, Reader), 309 | amqp10_client_frame_reader:set_connection(Reader, self()), 310 | State#state{sessions_sup = SessionsSup, 311 | reader_m_ref = ReaderMRef, 312 | reader = Reader}. 313 | 314 | terminate(Reason, _StateName, #state{connection_sup = Sup, 315 | config = Config}) -> 316 | ok = notify_closed(Config, Reason), 317 | case Reason of 318 | normal -> sys:terminate(Sup, normal); 319 | _ -> ok 320 | end, 321 | ok. 322 | 323 | code_change(_OldVsn, StateName, State, _Extra) -> 324 | {ok, StateName, State}. 325 | 326 | %% ------------------------------------------------------------------- 327 | %% Internal functions. 328 | %% ------------------------------------------------------------------- 329 | 330 | handle_begin_session({FromPid, _Ref}, 331 | #state{sessions_sup = Sup, reader = Reader, 332 | next_channel = Channel, 333 | config = Config} = State) -> 334 | Ret = supervisor:start_child(Sup, [FromPid, Channel, Reader, Config]), 335 | State1 = case Ret of 336 | {ok, _} -> State#state{next_channel = Channel + 1}; 337 | _ -> State 338 | end, 339 | {Ret, State1}. 340 | 341 | send_open(#state{socket = Socket, config = Config}) -> 342 | {ok, Product} = application:get_key(description), 343 | {ok, Version} = application:get_key(vsn), 344 | Platform = "Erlang/OTP " ++ erlang:system_info(otp_release), 345 | Props = {map, [{{symbol, <<"product">>}, 346 | {utf8, list_to_binary(Product)}}, 347 | {{symbol, <<"version">>}, 348 | {utf8, list_to_binary(Version)}}, 349 | {{symbol, <<"platform">>}, 350 | {utf8, list_to_binary(Platform)}} 351 | ]}, 352 | ContainerId = maps:get(container_id, Config, generate_container_id()), 353 | IdleTimeOut = maps:get(idle_time_out, Config, 0), 354 | Open0 = #'v1_0.open'{container_id = {utf8, ContainerId}, 355 | channel_max = {ushort, 100}, 356 | idle_time_out = {uint, IdleTimeOut}, 357 | properties = Props}, 358 | Open1 = case Config of 359 | #{max_frame_size := MFSz} -> 360 | Open0#'v1_0.open'{max_frame_size = {uint, MFSz}}; 361 | _ -> Open0 362 | end, 363 | Open = case Config of 364 | #{hostname := Hostname} -> 365 | Open1#'v1_0.open'{hostname = {utf8, Hostname}}; 366 | _ -> Open1 367 | end, 368 | Encoded = amqp10_framing:encode_bin(Open), 369 | Frame = amqp10_binary_generator:build_frame(0, Encoded), 370 | ?DBG("CONN <- ~p~n", [Open]), 371 | socket_send(Socket, Frame). 372 | 373 | 374 | send_close(#state{socket = Socket}, _Reason) -> 375 | Close = #'v1_0.close'{}, 376 | Encoded = amqp10_framing:encode_bin(Close), 377 | Frame = amqp10_binary_generator:build_frame(0, Encoded), 378 | ?DBG("CONN <- ~p~n", [Close]), 379 | Ret = socket_send(Socket, Frame), 380 | case Ret of 381 | ok -> _ = 382 | socket_shutdown(Socket, write), 383 | ok; 384 | _ -> ok 385 | end, 386 | Ret. 387 | 388 | send_sasl_init(State, anon) -> 389 | Frame = #'v1_0.sasl_init'{mechanism = {symbol, <<"ANONYMOUS">>}}, 390 | send(Frame, 1, State); 391 | send_sasl_init(State, {plain, User, Pass}) -> 392 | Response = <<0:8, User/binary, 0:8, Pass/binary>>, 393 | Frame = #'v1_0.sasl_init'{mechanism = {symbol, <<"PLAIN">>}, 394 | initial_response = {binary, Response}}, 395 | send(Frame, 1, State). 396 | 397 | send(Record, FrameType, #state{socket = Socket}) -> 398 | Encoded = amqp10_framing:encode_bin(Record), 399 | Frame = amqp10_binary_generator:build_frame(0, FrameType, Encoded), 400 | ?DBG("CONN <- ~p~n", [Record]), 401 | socket_send(Socket, Frame). 402 | 403 | send_heartbeat(#state{socket = Socket}) -> 404 | Frame = amqp10_binary_generator:build_heartbeat_frame(), 405 | socket_send(Socket, Frame). 406 | 407 | socket_send({tcp, Socket}, Data) -> 408 | gen_tcp:send(Socket, Data); 409 | socket_send({ssl, Socket}, Data) -> 410 | ssl:send(Socket, Data). 411 | 412 | socket_shutdown({tcp, Socket}, How) -> 413 | gen_tcp:shutdown(Socket, How); 414 | socket_shutdown({ssl, Socket}, How) -> 415 | ssl:shutdown(Socket, How). 416 | 417 | notify_opened(#{notify_when_opened := none}) -> 418 | ok; 419 | notify_opened(#{notify_when_opened := Pid}) when is_pid(Pid) -> 420 | Pid ! amqp10_event(opened), 421 | ok; 422 | notify_opened(#{notify := Pid}) when is_pid(Pid) -> 423 | Pid ! amqp10_event(opened), 424 | ok; 425 | notify_opened(_) -> 426 | ok. 427 | 428 | notify_closed(#{notify_when_closed := none}, _Reason) -> 429 | ok; 430 | notify_closed(#{notify := none}, _Reason) -> 431 | ok; 432 | notify_closed(#{notify_when_closed := Pid}, Reason) when is_pid(Pid) -> 433 | Pid ! amqp10_event({closed, Reason}), 434 | ok; 435 | notify_closed(#{notify := Pid}, Reason) when is_pid(Pid) -> 436 | Pid ! amqp10_event({closed, Reason}), 437 | ok. 438 | 439 | start_heartbeat_timer(Timeout) -> 440 | timer:apply_after(Timeout, ?MODULE, heartbeat, [self()]). 441 | 442 | unpack(V) -> amqp10_client_types:unpack(V). 443 | 444 | -spec generate_container_id() -> binary(). 445 | generate_container_id() -> 446 | Pre = list_to_binary(atom_to_list(node())), 447 | Id = bin_to_hex(crypto:strong_rand_bytes(8)), 448 | <
>/binary, Id/binary>>.
449 | 
450 | bin_to_hex(Bin) ->
451 |     <<<= 10 -> N -10 + $a;
452 |            true  -> N + $0 end>>
453 |       || <> <= Bin>>.
454 | 
455 | translate_err(undefined) ->
456 |     none;
457 | translate_err(#'v1_0.error'{condition = Cond, description = Desc}) ->
458 |     Err =
459 |         case Cond of
460 |             ?V_1_0_AMQP_ERROR_INTERNAL_ERROR -> internal_error;
461 |             ?V_1_0_AMQP_ERROR_NOT_FOUND -> not_found;
462 |             ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS -> unauthorized_access;
463 |             ?V_1_0_AMQP_ERROR_DECODE_ERROR -> decode_error;
464 |             ?V_1_0_AMQP_ERROR_RESOURCE_LIMIT_EXCEEDED -> resource_limit_exceeded;
465 |             ?V_1_0_AMQP_ERROR_NOT_ALLOWED -> not_allowed;
466 |             ?V_1_0_AMQP_ERROR_INVALID_FIELD -> invalid_field;
467 |             ?V_1_0_AMQP_ERROR_NOT_IMPLEMENTED -> not_implemented;
468 |             ?V_1_0_AMQP_ERROR_RESOURCE_LOCKED -> resource_locked;
469 |             ?V_1_0_AMQP_ERROR_PRECONDITION_FAILED -> precondition_failed;
470 |             ?V_1_0_AMQP_ERROR_RESOURCE_DELETED -> resource_deleted;
471 |             ?V_1_0_AMQP_ERROR_ILLEGAL_STATE -> illegal_state;
472 |             ?V_1_0_AMQP_ERROR_FRAME_SIZE_TOO_SMALL -> frame_size_too_small;
473 |             ?V_1_0_CONNECTION_ERROR_CONNECTION_FORCED -> forced;
474 |             ?V_1_0_CONNECTION_ERROR_FRAMING_ERROR -> framing_error;
475 |             ?V_1_0_CONNECTION_ERROR_REDIRECT -> redirect;
476 |             _ -> Cond
477 |         end,
478 |     {Err, unpack(Desc)}.
479 | 
480 | amqp10_event(Evt) ->
481 |     {amqp10_event, {connection, self(), Evt}}.
482 | 
483 | sasl_to_bin({plain, _, _}) -> <<"PLAIN">>;
484 | sasl_to_bin(anon) -> <<"ANONYMOUS">>.
485 | 
486 | config_defaults() ->
487 |     #{sasl => none,
488 |       transfer_limit_margin => 0,
489 |       max_frame_size => ?MAX_MAX_FRAME_SIZE}.
490 | 


--------------------------------------------------------------------------------
/src/amqp10_client_connection_sup.erl:
--------------------------------------------------------------------------------
 1 | %% This Source Code Form is subject to the terms of the Mozilla Public
 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
 4 | %%
 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
 6 | %%
 7 | -module(amqp10_client_connection_sup).
 8 | 
 9 | -behaviour(supervisor).
10 | 
11 | %% Private API.
12 | -export([start_link/1]).
13 | 
14 | %% Supervisor callbacks.
15 | -export([init/1]).
16 | 
17 | -define(CHILD(Id, Mod, Type, Args), {Id, {Mod, start_link, Args},
18 |                                      transient, 5000, Type, [Mod]}).
19 | 
20 | %% -------------------------------------------------------------------
21 | %% Private API.
22 | %% -------------------------------------------------------------------
23 | 
24 | -spec start_link(amqp10_client_connection:connection_config()) ->
25 |     {ok, pid()} | ignore | {error, any()}.
26 | start_link(Config) ->
27 |     supervisor:start_link(?MODULE, [Config]).
28 | 
29 | %% -------------------------------------------------------------------
30 | %% Supervisor callbacks.
31 | %% -------------------------------------------------------------------
32 | 
33 | init(Args) ->
34 |     ReaderSpec = ?CHILD(reader, amqp10_client_frame_reader,
35 |                         worker, [self() | Args]),
36 |     ConnectionSpec = ?CHILD(connection, amqp10_client_connection,
37 |                             worker, [self() | Args]),
38 |     SessionsSupSpec = ?CHILD(sessions, amqp10_client_sessions_sup,
39 |                              supervisor, []),
40 |     {ok, {{one_for_all, 0, 1}, [ConnectionSpec,
41 |                                 ReaderSpec,
42 |                                 SessionsSupSpec]}}.
43 | 


--------------------------------------------------------------------------------
/src/amqp10_client_connections_sup.erl:
--------------------------------------------------------------------------------
 1 | %% This Source Code Form is subject to the terms of the Mozilla Public
 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
 4 | %%
 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
 6 | %%
 7 | -module(amqp10_client_connections_sup).
 8 | 
 9 | -behaviour(supervisor).
10 | 
11 | %% Private API.
12 | -export([start_link/0,
13 |          stop_child/1]).
14 | 
15 | %% Supervisor callbacks.
16 | -export([init/1]).
17 | 
18 | -define(CHILD(Id, Mod, Type, Args), {Id, {Mod, start_link, Args},
19 |                                      temporary, infinity, Type, [Mod]}).
20 | 
21 | %% -------------------------------------------------------------------
22 | %% Private API.
23 | %% -------------------------------------------------------------------
24 | 
25 | stop_child(Pid) ->
26 |     supervisor:terminate_child({local, ?MODULE}, Pid).
27 | 
28 | start_link() ->
29 |     supervisor:start_link({local, ?MODULE}, ?MODULE, []).
30 | 
31 | %% -------------------------------------------------------------------
32 | %% Supervisor callbacks.
33 | %% -------------------------------------------------------------------
34 | 
35 | init([]) ->
36 |     Template = ?CHILD(connection_sup, amqp10_client_connection_sup,
37 |                       supervisor, []),
38 |     {ok, {{simple_one_for_one, 0, 1}, [Template]}}.
39 | 


--------------------------------------------------------------------------------
/src/amqp10_client_frame_reader.erl:
--------------------------------------------------------------------------------
  1 | %% This Source Code Form is subject to the terms of the Mozilla Public
  2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
  3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
  4 | %%
  5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
  6 | %%
  7 | -module(amqp10_client_frame_reader).
  8 | 
  9 | -behaviour(gen_statem).
 10 | 
 11 | -include("amqp10_client.hrl").
 12 | -include_lib("amqp10_common/include/amqp10_framing.hrl").
 13 | 
 14 | -ifdef(TEST).
 15 | -include_lib("eunit/include/eunit.hrl").
 16 | -endif.
 17 | 
 18 | %% API
 19 | -export([start_link/2,
 20 |          set_connection/2,
 21 |          close/1,
 22 |          register_session/3,
 23 |          unregister_session/4]).
 24 | 
 25 | %% gen_statem callbacks
 26 | -export([init/1,
 27 |          callback_mode/0,
 28 |          handle_event/4,
 29 |          code_change/4,
 30 |          terminate/3]).
 31 | 
 32 | -define(RABBIT_TCP_OPTS, [binary,
 33 |                           {packet, 0},
 34 |                           {active, false},
 35 |                           {nodelay, true}]).
 36 | 
 37 | -type frame_type() ::  amqp | sasl.
 38 | 
 39 | -record(frame_state,
 40 |         {data_offset :: 2..255,
 41 |          type :: frame_type(),
 42 |          channel :: non_neg_integer(),
 43 |          frame_length :: pos_integer()}).
 44 | 
 45 | -record(state,
 46 |         {connection_sup :: pid(),
 47 |          socket :: amqp10_client_connection:amqp10_socket() | undefined,
 48 |          buffer = <<>> :: binary(),
 49 |          frame_state :: #frame_state{} | undefined,
 50 |          connection :: pid() | undefined,
 51 |          heartbeat_timer_ref :: reference() | undefined,
 52 |          connection_config = #{} :: amqp10_client_connection:connection_config(),
 53 |          outgoing_channels = #{},
 54 |          incoming_channels = #{}}).
 55 | 
 56 | %%%===================================================================
 57 | %%% API
 58 | %%%===================================================================
 59 | 
 60 | -spec start_link(pid(), amqp10_client_connection:connection_config()) ->
 61 |     {ok, pid()} | ignore | {error, any()}.
 62 | start_link(Sup, Config) ->
 63 |     gen_statem:start_link(?MODULE, [Sup, Config], []).
 64 | 
 65 | %% @private
 66 | %% @doc
 67 | %% Passes the connection process PID to the reader process.
 68 | %%
 69 | %% This function is called when a connection supervision tree is
 70 | %% started.
 71 | -spec set_connection(Reader :: pid(), ConnectionPid :: pid()) -> ok.
 72 | set_connection(Reader, Connection) ->
 73 |     gen_statem:cast(Reader, {set_connection, Connection}).
 74 | 
 75 | close(Reader) ->
 76 |     gen_statem:cast(Reader, close).
 77 | 
 78 | register_session(Reader, Session, OutgoingChannel) ->
 79 |     gen_statem:cast(Reader, {register_session, Session, OutgoingChannel}).
 80 | 
 81 | unregister_session(Reader, Session, OutgoingChannel, IncomingChannel) ->
 82 |     gen_statem:cast(Reader, {unregister_session, Session, OutgoingChannel, IncomingChannel}).
 83 | 
 84 | %%%===================================================================
 85 | %%% gen_statem callbacks
 86 | %%%===================================================================
 87 | 
 88 | callback_mode() ->
 89 |     [handle_event_function].
 90 | 
 91 | init([Sup, ConnConfig]) when is_map(ConnConfig) ->
 92 |     Port = maps:get(port, ConnConfig, 5672),
 93 |     %% combined the list of `addresses' with the value of the original `address' option if provided
 94 |     Addresses0 = maps:get(addresses, ConnConfig, []),
 95 |     Addresses  = case maps:get(address, ConnConfig, undefined) of
 96 |                      undefined -> Addresses0;
 97 |                      Address   -> Addresses0 ++ [Address]
 98 |                  end,
 99 |     Result = lists:foldl(fun (Address,  {error, _}) ->
100 |                                 gen_tcp:connect(Address, Port, ?RABBIT_TCP_OPTS);
101 |                              (_Address, {ok, Socket}) ->
102 |                                  {ok, Socket}
103 |                          end,
104 |                          {error, undefined}, Addresses),
105 |     case Result of
106 |         {ok, Socket0} ->
107 |             Socket = case ConnConfig of
108 |                          #{tls_opts := {secure_port, Opts}} ->
109 |                              {ok, SslSock} = ssl:connect(Socket0, Opts),
110 |                              {ssl, SslSock};
111 |                          _ -> {tcp, Socket0}
112 |                      end,
113 |             State = #state{connection_sup = Sup, socket = Socket,
114 |                            connection_config = ConnConfig},
115 |             {ok, expecting_connection_pid, State};
116 |         {error, Reason} ->
117 |             {stop, Reason}
118 |     end.
119 | 
120 | handle_event(cast, {set_connection, ConnectionPid}, expecting_connection_pid,
121 |              State=#state{socket = Socket}) ->
122 |     ok = amqp10_client_connection:socket_ready(ConnectionPid, Socket),
123 |     set_active_once(State),
124 |     State1 = State#state{connection = ConnectionPid},
125 |     {next_state, expecting_frame_header, State1};
126 | handle_event(cast, {register_session, Session, OutgoingChannel}, _StateName,
127 |              #state{socket = Socket, outgoing_channels = OutgoingChannels} = State) ->
128 |     ok = amqp10_client_session:socket_ready(Session, Socket),
129 |     OutgoingChannels1 = OutgoingChannels#{OutgoingChannel => Session},
130 |     State1 = State#state{outgoing_channels = OutgoingChannels1},
131 |     {keep_state, State1};
132 | handle_event(cast, {unregister_session, _Session, OutgoingChannel, IncomingChannel}, _StateName,
133 |              State=#state{outgoing_channels = OutgoingChannels,
134 |                           incoming_channels = IncomingChannels}) ->
135 |     OutgoingChannels1 = maps:remove(OutgoingChannel, OutgoingChannels),
136 |     IncomingChannels1 = maps:remove(IncomingChannel, IncomingChannels),
137 |     State1 = State#state{outgoing_channels = OutgoingChannels1,
138 |                          incoming_channels = IncomingChannels1},
139 |     {keep_state, State1};
140 | handle_event(cast, close, _StateName, State = #state{socket = Socket}) ->
141 |     close_socket(Socket),
142 |     {stop, normal, State#state{socket = undefined}};
143 | 
144 | handle_event({call, From}, _Action, _State, _Data) ->
145 |     {keep_state_and_data, [{reply, From, ok}]};
146 | 
147 | handle_event(info, {Tcp, _, Packet}, StateName, #state{buffer = Buffer} = State)
148 |   when Tcp == tcp orelse Tcp == ssl ->
149 |     Data = <>,
150 |     case handle_input(StateName, Data, State) of
151 |         {ok, NextState, Remaining, NewState0} ->
152 |             NewState = defer_heartbeat_timer(NewState0),
153 |             set_active_once(NewState),
154 |             {next_state, NextState, NewState#state{buffer = Remaining}};
155 |         {error, Reason, NewState} ->
156 |             {stop, Reason, NewState}
157 |     end;
158 | 
159 | handle_event(info, {TcpError, _, Reason}, StateName, State)
160 |   when TcpError == tcp_error orelse TcpError == ssl_error ->
161 |     error_logger:warning_msg("AMQP 1.0 connection socket errored, connection state: '~s', reason: '~p'~n",
162 |                              [StateName, Reason]),
163 |     State1 = State#state{socket = undefined,
164 |                          buffer = <<>>,
165 |                          frame_state = undefined},
166 |     {stop, {error, Reason}, State1};
167 | handle_event(info, {TcpClosed, _}, StateName, State)
168 |   when TcpClosed == tcp_closed orelse TcpClosed == ssl_closed ->
169 |     error_logger:warning_msg("AMQP 1.0 connection socket was closed, connection state: '~s'~n",
170 |                              [StateName]),
171 |     State1 = State#state{socket = undefined,
172 |                          buffer = <<>>,
173 |                          frame_state = undefined},
174 |     {stop, normal, State1};
175 | 
176 | handle_event(info, heartbeat, _StateName, #state{connection = Connection}) ->
177 |     amqp10_client_connection:close(Connection,
178 |                                    {resource_limit_exceeded, <<"remote idle-time-out">>}),
179 |     % do not stop as may want to read the peer's close frame
180 |     keep_state_and_data.
181 | 
182 | terminate(normal, _StateName, #state{connection_sup = _Sup, socket = Socket}) ->
183 |     maybe_close_socket(Socket);
184 | terminate(_Reason, _StateName, #state{connection_sup = _Sup, socket = Socket}) ->
185 |     maybe_close_socket(Socket).
186 | 
187 | code_change(_Vsn, State, Data, _Extra) ->
188 |     {ok, State, Data}.
189 | 
190 | %%%===================================================================
191 | %%% Internal functions
192 | %%%===================================================================
193 | 
194 | maybe_close_socket(undefined) ->
195 |     ok;
196 | maybe_close_socket(Socket) ->
197 |     close_socket(Socket).
198 | 
199 | close_socket({tcp, Socket}) ->
200 |     gen_tcp:close(Socket);
201 | close_socket({ssl, Socket}) ->
202 |     ssl:close(Socket).
203 | 
204 | set_active_once(#state{socket = {tcp, Socket}}) ->
205 |     ok = inet:setopts(Socket, [{active, once}]);
206 | set_active_once(#state{socket = {ssl, Socket}}) ->
207 |     ok = ssl:setopts(Socket, [{active, once}]).
208 | 
209 | handle_input(expecting_frame_header,
210 |              <<"AMQP", Protocol/unsigned, Maj/unsigned, Min/unsigned,
211 |                Rev/unsigned, Rest/binary>>,
212 |              #state{connection = ConnectionPid} = State)
213 |   when Protocol =:= 0 orelse Protocol =:= 3 ->
214 |     ok = amqp10_client_connection:protocol_header_received(
215 |            ConnectionPid, Protocol, Maj, Min, Rev),
216 |     handle_input(expecting_frame_header, Rest, State);
217 | 
218 | handle_input(expecting_frame_header,
219 |              <>, State)
221 |   when DOff >= 2 andalso (Type =:= 0 orelse Type =:= 1) ->
222 |     AFS = #frame_state{frame_length = Length, channel = Channel,
223 |                        type = frame_type(Type), data_offset = DOff},
224 |     handle_input(expecting_extended_frame_header, Rest,
225 |                  State#state{frame_state = AFS});
226 | 
227 | handle_input(expecting_frame_header, <<_:8/binary, _/binary>>, State) ->
228 |     {error, invalid_protocol_header, State};
229 | 
230 | handle_input(expecting_extended_frame_header, Data,
231 |              #state{frame_state =
232 |                     #frame_state{data_offset = DOff}} = State) ->
233 |     Skip = DOff * 4 - 8,
234 |     case Data of
235 |         <<_:Skip/binary, Rest/binary>> ->
236 |             handle_input(expecting_frame_body, Rest, State);
237 |         _ ->
238 |             {ok, expecting_extended_frame_header, Data, State}
239 |     end;
240 | 
241 | handle_input(expecting_frame_body, Data,
242 |              #state{frame_state = #frame_state{frame_length = Length,
243 |                                                type = FrameType,
244 |                                                data_offset = DOff,
245 |                                                channel = Channel}} = State) ->
246 |     Skip = DOff * 4 - 8,
247 |     BodyLength = Length - Skip - 8,
248 |     case {Data, BodyLength} of
249 |         {<<_:BodyLength/binary, Rest/binary>>, 0} ->
250 |             % heartbeat
251 |             handle_input(expecting_frame_header, Rest, State);
252 |         {<>, _} ->
253 |             State1 = State#state{frame_state = undefined},
254 |             {PerfDesc, Payload} = amqp10_binary_parser:parse(FrameBody),
255 |             Perf = amqp10_framing:decode(PerfDesc),
256 |             State2 = route_frame(Channel, FrameType, {Perf, Payload}, State1),
257 |             handle_input(expecting_frame_header, Rest, State2);
258 |         _ ->
259 |             {ok, expecting_frame_body, Data, State}
260 |     end;
261 | 
262 | handle_input(StateName, Data, State) ->
263 |     {ok, StateName, Data, State}.
264 | 
265 | %%% LOCAL
266 | 
267 | defer_heartbeat_timer(State =
268 |                       #state{heartbeat_timer_ref = TRef,
269 |                              connection_config = #{idle_time_out := T}})
270 |   when is_number(T) andalso T > 0 ->
271 |     _ = case TRef of
272 |             undefined -> ok;
273 |             _ -> _ = erlang:cancel_timer(TRef)
274 |         end,
275 |     NewTRef = erlang:send_after(T * 2, self(), heartbeat),
276 |     State#state{heartbeat_timer_ref = NewTRef};
277 | defer_heartbeat_timer(State) -> State.
278 | 
279 | route_frame(Channel, FrameType, {Performative, Payload} = Frame, State0) ->
280 |     {DestinationPid, State} = find_destination(Channel, FrameType, Performative,
281 |                                                State0),
282 |     ?DBG("FRAME -> ~p ~p~n ~p~n", [Channel, DestinationPid, Performative]),
283 |     case Payload of
284 |         <<>> -> ok = gen_statem:cast(DestinationPid, Performative);
285 |         _ -> ok = gen_statem:cast(DestinationPid, Frame)
286 |     end,
287 |     State.
288 | 
289 | -spec find_destination(amqp10_client_types:channel(), frame_type(),
290 |                        amqp10_client_types:amqp10_performative(), #state{}) ->
291 |     {pid(), #state{}}.
292 | find_destination(0, amqp, Frame, #state{connection = ConnPid} = State)
293 |     when is_record(Frame, 'v1_0.open') orelse
294 |          is_record(Frame, 'v1_0.close') ->
295 |     {ConnPid, State};
296 | find_destination(_Channel, sasl, _Frame,
297 |                  #state{connection = ConnPid} = State) ->
298 |     {ConnPid, State};
299 | find_destination(Channel, amqp,
300 |                  #'v1_0.begin'{remote_channel = {ushort, OutgoingChannel}},
301 |                  #state{outgoing_channels = OutgoingChannels,
302 |                         incoming_channels = IncomingChannels} = State) ->
303 |     #{OutgoingChannel := Session} = OutgoingChannels,
304 |     IncomingChannels1 = IncomingChannels#{Channel => Session},
305 |     State1 = State#state{incoming_channels = IncomingChannels1},
306 |     {Session, State1};
307 | find_destination(Channel, amqp, _Frame,
308 |                 #state{incoming_channels = IncomingChannels} = State) ->
309 |     #{Channel := Session} = IncomingChannels,
310 |     {Session, State}.
311 | 
312 | frame_type(0) -> amqp;
313 | frame_type(1) -> sasl.
314 | 
315 | -ifdef(TEST).
316 | 
317 | find_destination_test_() ->
318 |     Pid = self(),
319 |     State = #state{connection = Pid, outgoing_channels = #{3 => Pid}},
320 |     StateConn = #state{connection = Pid},
321 |     StateWithIncoming = State#state{incoming_channels = #{7 => Pid}},
322 |     StateWithIncoming0 = State#state{incoming_channels = #{0 => Pid}},
323 |     Tests = [{0, #'v1_0.open'{}, State, State, amqp},
324 |              {0, #'v1_0.close'{}, State, State, amqp},
325 |              {7, #'v1_0.begin'{remote_channel = {ushort, 3}}, State,
326 |               StateWithIncoming, amqp},
327 |              {0, #'v1_0.begin'{remote_channel = {ushort, 3}}, State,
328 |               StateWithIncoming0, amqp},
329 |              {7, #'v1_0.end'{}, StateWithIncoming, StateWithIncoming, amqp},
330 |              {7, #'v1_0.attach'{}, StateWithIncoming, StateWithIncoming, amqp},
331 |              {7, #'v1_0.flow'{}, StateWithIncoming, StateWithIncoming, amqp},
332 |              {0, #'v1_0.sasl_init'{}, StateConn, StateConn, sasl}
333 |             ],
334 |     [?_assertMatch({Pid, NewState},
335 |                    find_destination(Channel, Type, Frame, InputState))
336 |      || {Channel, Frame, InputState, NewState, Type} <- Tests].
337 | 
338 | -endif.
339 | 


--------------------------------------------------------------------------------
/src/amqp10_client_sessions_sup.erl:
--------------------------------------------------------------------------------
 1 | %% This Source Code Form is subject to the terms of the Mozilla Public
 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
 4 | %%
 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
 6 | %%
 7 | -module(amqp10_client_sessions_sup).
 8 | 
 9 | -behaviour(supervisor).
10 | 
11 | %% Private API.
12 | -export([start_link/0]).
13 | 
14 | %% Supervisor callbacks.
15 | -export([init/1]).
16 | 
17 | -define(CHILD(Id, Mod, Type, Args), {Id, {Mod, start_link, Args},
18 |                                      transient, 5000, Type, [Mod]}).
19 | 
20 | %% -------------------------------------------------------------------
21 | %% Private API.
22 | %% -------------------------------------------------------------------
23 | 
24 | -spec start_link() ->
25 |     {ok, pid()} | ignore | {error, any()}.
26 | 
27 | start_link() ->
28 |     supervisor:start_link(?MODULE, []).
29 | 
30 | %% -------------------------------------------------------------------
31 | %% Supervisor callbacks.
32 | %% -------------------------------------------------------------------
33 | 
34 | init(Args) ->
35 |     Template = ?CHILD(session, amqp10_client_session, worker, Args),
36 |     {ok, {{simple_one_for_one, 0, 1}, [Template]}}.
37 | 


--------------------------------------------------------------------------------
/src/amqp10_client_sup.erl:
--------------------------------------------------------------------------------
 1 | %% This Source Code Form is subject to the terms of the Mozilla Public
 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
 4 | %%
 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
 6 | %%
 7 | -module(amqp10_client_sup).
 8 | 
 9 | -behaviour(supervisor).
10 | 
11 | %% Private API.
12 | -export([start_link/0]).
13 | 
14 | %% Supervisor callbacks.
15 | -export([init/1]).
16 | 
17 | -define(CHILD(Id, Mod, Type, Args), {Id, {Mod, start_link, Args},
18 |                                      temporary, infinity, Type, [Mod]}).
19 | 
20 | %% -------------------------------------------------------------------
21 | %% Private API.
22 | %% -------------------------------------------------------------------
23 | 
24 | start_link() ->
25 |     supervisor:start_link({local, ?MODULE}, ?MODULE, []).
26 | 
27 | %% -------------------------------------------------------------------
28 | %% Supervisor callbacks.
29 | %% -------------------------------------------------------------------
30 | 
31 | init([]) ->
32 |     Template = ?CHILD(connection_sup, amqp10_client_connection_sup,
33 |                       supervisor, []),
34 |     {ok, {{simple_one_for_one, 0, 1}, [Template]}}.
35 | 


--------------------------------------------------------------------------------
/src/amqp10_client_types.erl:
--------------------------------------------------------------------------------
 1 | %% This Source Code Form is subject to the terms of the Mozilla Public
 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
 4 | %%
 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
 6 | %%
 7 | -module(amqp10_client_types).
 8 | 
 9 | -include_lib("amqp10_common/include/amqp10_framing.hrl").
10 | 
11 | -export([unpack/1,
12 |          utf8/1,
13 |          uint/1]).
14 | 
15 | -type amqp10_performative() :: #'v1_0.open'{} | #'v1_0.begin'{} | #'v1_0.attach'{} |
16 |                                #'v1_0.flow'{} | #'v1_0.transfer'{} |
17 |                                #'v1_0.disposition'{} | #'v1_0.detach'{} |
18 |                                #'v1_0.end'{} | #'v1_0.close'{}.
19 | 
20 | -type amqp10_msg_record() :: #'v1_0.transfer'{} | #'v1_0.header'{} |
21 |                              #'v1_0.delivery_annotations'{} |
22 |                              #'v1_0.message_annotations'{} |
23 |                              #'v1_0.properties'{} |
24 |                              #'v1_0.application_properties'{} |
25 |                              #'v1_0.data'{} | #'v1_0.amqp_sequence'{} |
26 |                              #'v1_0.amqp_value'{} | #'v1_0.footer'{}.
27 | 
28 | -type channel() :: non_neg_integer().
29 | 
30 | -type source() :: #'v1_0.source'{}.
31 | -type target() :: #'v1_0.target'{}.
32 | 
33 | -type delivery_state() :: accepted | rejected | modified | received | released.
34 | 
35 | -type amqp_error() :: internal_error | not_found | unauthorized_access |
36 |                       decode_error | resource_limit_exceeded |
37 |                       not_allowed | invalid_field | not_implemented |
38 |                       resource_locked | precondition_failed | resource_deleted |
39 |                       illegal_state | frame_size_too_small.
40 | 
41 | -type connection_error()  :: connection_forced | framing_error | redirect.
42 | -type session_error() :: atom(). % TODO
43 | -type link_error() :: atom(). % TODO
44 | 
45 | -type connection_event_detail() :: opened |
46 |                                    {closed, Reason::any()} |
47 |                                    {error, {connection_error(), any()}}.
48 | -type session_event_detail() :: begun | ended | {error, {session_error(), any()}}.
49 | -type link_event_detail() :: attached | detached | {error, {link_error(), any()}}.
50 | -type amqp10_event_detail() :: {connection, pid(), connection_event_detail()} |
51 |                                {session, pid(), session_event_detail()} |
52 |                                {link, {sender | receiver, Name :: binary()},
53 |                                 link_event_detail()}.
54 | -type amqp10_event() :: {amqp10_event, amqp10_event_detail()}.
55 | 
56 | -export_type([amqp10_performative/0, channel/0,
57 |               source/0, target/0, amqp10_msg_record/0,
58 |               delivery_state/0, amqp_error/0, connection_error/0,
59 |               amqp10_event_detail/0, amqp10_event/0]).
60 | 
61 | 
62 | unpack(undefined) -> undefined;
63 | unpack({_, Value}) -> Value;
64 | unpack(Value) -> Value.
65 | 
66 | utf8(S) when is_list(S) -> {utf8, list_to_binary(S)};
67 | utf8(B) when is_binary(B) -> {utf8, B}.
68 | 
69 | uint(N) -> {uint, N}.
70 | 


--------------------------------------------------------------------------------
/src/amqp10_msg.erl:
--------------------------------------------------------------------------------
  1 | %% This Source Code Form is subject to the terms of the Mozilla Public
  2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
  3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
  4 | %%
  5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
  6 | %%
  7 | -module(amqp10_msg).
  8 | 
  9 | -export([from_amqp_records/1,
 10 |          to_amqp_records/1,
 11 |          % "read" api
 12 |          delivery_id/1,
 13 |          delivery_tag/1,
 14 |          handle/1,
 15 |          settled/1,
 16 |          message_format/1,
 17 |          headers/1,
 18 |          header/2,
 19 |          delivery_annotations/1,
 20 |          message_annotations/1,
 21 |          properties/1,
 22 |          application_properties/1,
 23 |          body/1,
 24 |          body_bin/1,
 25 |          footer/1,
 26 |          % "write" api
 27 |          new/2,
 28 |          new/3,
 29 |          set_handle/2,
 30 |          set_settled/2,
 31 |          set_message_format/2,
 32 |          set_headers/2,
 33 |          set_properties/2,
 34 |          set_application_properties/2,
 35 |          set_delivery_annotations/2,
 36 |          set_message_annotations/2
 37 |         ]).
 38 | 
 39 | -include_lib("amqp10_common/include/amqp10_framing.hrl").
 40 | 
 41 | -type maybe(T) :: T | undefined.
 42 | 
 43 | -type delivery_tag() :: binary().
 44 | -type content_type() :: term(). % TODO: refine
 45 | -type content_encoding() :: term(). % TODO: refine
 46 | 
 47 | % annotations keys are restricted to be of type symbol or of type ulong
 48 | -type annotations_key() :: binary() | non_neg_integer().
 49 | 
 50 | -type header_key() :: durable | priority | ttl | first_acquirer |
 51 |                       delivery_count.
 52 | 
 53 | -type amqp10_header() :: #{durable => boolean(), % false
 54 |                            priority => byte(), % 4
 55 |                            ttl => maybe(non_neg_integer()),
 56 |                            first_acquirer => boolean(), % false
 57 |                            delivery_count => non_neg_integer()}. % 0
 58 | 
 59 | -type amqp10_properties() :: #{message_id => maybe(any()),
 60 |                                user_id => maybe(binary()),
 61 |                                to => maybe(any()),
 62 |                                subject => maybe(binary()),
 63 |                                reply_to => maybe(any()),
 64 |                                correlation_id => maybe(any()),
 65 |                                content_type => maybe(content_type()),
 66 |                                content_encoding => maybe(content_encoding()),
 67 |                                absolute_expiry_time => maybe(non_neg_integer()),
 68 |                                creation_time => maybe(non_neg_integer()),
 69 |                                group_id => maybe(binary()),
 70 |                                group_sequence => maybe(non_neg_integer()),
 71 |                                reply_to_group_id => maybe(binary())}.
 72 | 
 73 | -type amqp10_body() :: [#'v1_0.data'{}] |
 74 |                        [#'v1_0.amqp_sequence'{}] |
 75 |                        #'v1_0.amqp_value'{}.
 76 | 
 77 | 
 78 | 
 79 | -record(amqp10_msg,
 80 |         {transfer :: #'v1_0.transfer'{},
 81 |          header :: maybe(#'v1_0.header'{}),
 82 |          delivery_annotations :: maybe(#'v1_0.delivery_annotations'{}),
 83 |          message_annotations :: maybe(#'v1_0.message_annotations'{}),
 84 |          properties :: maybe(#'v1_0.properties'{}),
 85 |          application_properties :: maybe(#'v1_0.application_properties'{}),
 86 |          body :: amqp10_body() | unset,
 87 |          footer :: maybe(#'v1_0.footer'{})
 88 |          }).
 89 | 
 90 | -opaque amqp10_msg() :: #amqp10_msg{}.
 91 | 
 92 | -export_type([amqp10_msg/0,
 93 |               amqp10_header/0,
 94 |               amqp10_properties/0,
 95 |               amqp10_body/0,
 96 |               delivery_tag/0
 97 |              ]).
 98 | 
 99 | -define(record_to_tuplelist(Rec, Ref),
100 |         lists:zip(record_info(fields, Rec), tl(tuple_to_list(Ref)))).
101 | 
102 | 
103 | %% API functions
104 | 
105 | -spec from_amqp_records([amqp10_client_types:amqp10_msg_record()]) ->
106 |     amqp10_msg().
107 | from_amqp_records([#'v1_0.transfer'{} = Transfer | Records]) ->
108 |     lists:foldl(fun parse_from_amqp/2, #amqp10_msg{transfer = Transfer,
109 |                                                    body = unset}, Records).
110 | 
111 | -spec to_amqp_records(amqp10_msg()) -> [amqp10_client_types:amqp10_msg_record()].
112 | to_amqp_records(#amqp10_msg{transfer = T,
113 |                             header = H,
114 |                             delivery_annotations = DAs,
115 |                             message_annotations = MAs,
116 |                             properties = Ps,
117 |                             application_properties = APs,
118 |                             body = B,
119 |                             footer = F
120 |                             }) ->
121 |     L = lists:flatten([T, H, DAs, MAs, Ps, APs, B, F]),
122 |     lists:filter(fun has_value/1, L).
123 | 
124 | -spec delivery_tag(amqp10_msg()) -> delivery_tag().
125 | delivery_tag(#amqp10_msg{transfer = #'v1_0.transfer'{delivery_tag = Tag}}) ->
126 |     unpack(Tag).
127 | 
128 | -spec delivery_id(amqp10_msg()) -> non_neg_integer().
129 | delivery_id(#amqp10_msg{transfer = #'v1_0.transfer'{delivery_id = Id}}) ->
130 |     unpack(Id).
131 | 
132 | -spec handle(amqp10_msg()) -> non_neg_integer().
133 | handle(#amqp10_msg{transfer = #'v1_0.transfer'{handle = Handle}}) ->
134 |     unpack(Handle).
135 | 
136 | -spec settled(amqp10_msg()) -> boolean().
137 | settled(#amqp10_msg{transfer = #'v1_0.transfer'{settled = Settled}}) ->
138 |     Settled.
139 | 
140 | % First 3 octets are the format
141 | % the last 1 octet is the version
142 | % See 2.8.11 in the spec
143 | -spec message_format(amqp10_msg()) ->
144 |     maybe({non_neg_integer(), non_neg_integer()}).
145 | message_format(#amqp10_msg{transfer =
146 |                          #'v1_0.transfer'{message_format = undefined}}) ->
147 |     undefined;
148 | message_format(#amqp10_msg{transfer =
149 |                          #'v1_0.transfer'{message_format = {uint, MF}}}) ->
150 |     <> = <>,
151 |     {Format, Version}.
152 | 
153 | 
154 | -spec headers(amqp10_msg()) -> amqp10_header().
155 | headers(#amqp10_msg{header = undefined}) -> #{};
156 | headers(#amqp10_msg{header = #'v1_0.header'{durable = Durable,
157 |                                             priority = Priority,
158 |                                             ttl = Ttl,
159 |                                             first_acquirer = FA,
160 |                                             delivery_count = DC}}) ->
161 |     Fields = [{durable, header_value(durable, Durable)},
162 |               {priority, header_value(priority, Priority)},
163 |               {ttl, header_value(ttl, Ttl)},
164 |               {first_acquirer, header_value(first_acquirer, FA)},
165 |               {delivery_count, header_value(delivery_count, DC)}],
166 | 
167 |     lists:foldl(fun ({_Key, undefined}, Acc) -> Acc;
168 |                     ({Key, Value}, Acc) -> Acc#{Key => Value}
169 |                 end, #{}, Fields).
170 | 
171 | -spec header(header_key(), amqp10_msg()) -> term().
172 | header(durable = K, #amqp10_msg{header = #'v1_0.header'{durable = D}}) ->
173 |     header_value(K, D);
174 | header(priority = K,
175 |        #amqp10_msg{header = #'v1_0.header'{priority = D}}) ->
176 |     header_value(K, D);
177 | header(ttl = K, #amqp10_msg{header = #'v1_0.header'{ttl = D}}) ->
178 |     header_value(K, D);
179 | header(first_acquirer = K,
180 |        #amqp10_msg{header = #'v1_0.header'{first_acquirer = D}}) ->
181 |     header_value(K, D);
182 | header(delivery_count = K,
183 |        #amqp10_msg{header = #'v1_0.header'{delivery_count = D}}) ->
184 |     header_value(K, D);
185 | header(K, #amqp10_msg{header = undefined}) -> header_value(K, undefined).
186 | 
187 | -spec delivery_annotations(amqp10_msg()) -> #{annotations_key() => any()}.
188 | delivery_annotations(#amqp10_msg{delivery_annotations = undefined}) ->
189 |     #{};
190 | delivery_annotations(#amqp10_msg{delivery_annotations =
191 |                                #'v1_0.delivery_annotations'{content = DAs}}) ->
192 |     lists:foldl(fun({K, V}, Acc) -> Acc#{unpack(K) => unpack(V)} end,
193 |                 #{}, DAs).
194 | 
195 | -spec message_annotations(amqp10_msg()) -> #{annotations_key() => any()}.
196 | message_annotations(#amqp10_msg{message_annotations = undefined}) ->
197 |     #{};
198 | message_annotations(#amqp10_msg{message_annotations =
199 |                                #'v1_0.message_annotations'{content = MAs}}) ->
200 |     lists:foldl(fun({K, V}, Acc) -> Acc#{unpack(K) => unpack(V)} end,
201 |                 #{}, MAs).
202 | 
203 | -spec properties(amqp10_msg()) -> amqp10_properties().
204 | properties(#amqp10_msg{properties = undefined}) -> #{};
205 | properties(#amqp10_msg{properties = Props}) ->
206 |     Fields = ?record_to_tuplelist('v1_0.properties', Props),
207 |     lists:foldl(fun ({_Key, undefined}, Acc) -> Acc;
208 |                     ({Key, Value}, Acc) -> Acc#{Key => unpack(Value)}
209 |                 end, #{}, Fields).
210 | 
211 | % application property values can be simple types - no maps or lists
212 | -spec application_properties(amqp10_msg()) ->
213 |     #{binary() => binary() | integer() | string()}.
214 | application_properties(#amqp10_msg{application_properties = undefined}) ->
215 |     #{};
216 | application_properties(
217 |   #amqp10_msg{application_properties =
218 |             #'v1_0.application_properties'{content = MAs}}) ->
219 |     lists:foldl(fun({K, V}, Acc) -> Acc#{unpack(K) => unpack(V)} end,
220 |                 #{}, MAs).
221 | 
222 | -spec footer(amqp10_msg()) -> #{annotations_key() => any()}.
223 | footer(#amqp10_msg{footer = undefined}) -> #{};
224 | footer(#amqp10_msg{footer = #'v1_0.footer'{content = Footer}}) ->
225 |     lists:foldl(fun({K, V}, Acc) -> Acc#{unpack(K) => unpack(V)} end, #{},
226 |                 Footer).
227 | 
228 | -spec body(amqp10_msg()) ->
229 |     [binary()] | [#'v1_0.amqp_sequence'{}] | #'v1_0.amqp_value'{}.
230 | body(#amqp10_msg{body = [#'v1_0.data'{} | _] = Data}) ->
231 |     [Content || #'v1_0.data'{content = Content} <- Data];
232 | body(#amqp10_msg{body = Body}) -> Body.
233 | 
234 | %% @doc Returns the binary representation
235 | -spec body_bin(amqp10_msg()) -> binary().
236 | body_bin(#amqp10_msg{body = [#'v1_0.data'{content = Bin}]})
237 |   when is_binary(Bin) ->
238 |     Bin;
239 | body_bin(#amqp10_msg{body = Data}) when is_list(Data) ->
240 |     iolist_to_binary([amqp10_framing:encode_bin(D) || D <- Data]);
241 | body_bin(#amqp10_msg{body = #'v1_0.amqp_value'{} = Body}) ->
242 |     %% TODO: to avoid unnecessary decoding and re-encoding we could amend
243 |     %% the parse to provide the body in a lazy fashion, only decoding when
244 |     %% reading. For now we just re-encode it.
245 |     iolist_to_binary(amqp10_framing:encode_bin(Body)).
246 | 
247 | %% @doc Create a new amqp10 message using the specified delivery tag, body
248 | %% and settlement state. Settled=true means the message is considered settled
249 | %% as soon as sent and no disposition will be issued by the receiver.
250 | %% Settled=false will delay settlement until a disposition has been received.
251 | %% A disposition will be notified to the sender by a message of the
252 | %% following stucture:
253 | %% {amqp10_disposition, {accepted | rejected, DeliveryTag}}
254 | -spec new(delivery_tag(), amqp10_body() | binary(), boolean()) -> amqp10_msg().
255 | new(DeliveryTag, Body, Settled) when is_binary(Body) ->
256 |     #amqp10_msg{transfer = #'v1_0.transfer'{delivery_tag = {binary, DeliveryTag},
257 |                                             settled = Settled,
258 |                                             message_format = {uint, 0}},
259 |                 body = [#'v1_0.data'{content = Body}]};
260 | new(DeliveryTag, Body, Settled) -> % TODO: constrain to amqp types
261 |     #amqp10_msg{transfer = #'v1_0.transfer'{delivery_tag = {binary, DeliveryTag},
262 |                                             settled = Settled,
263 |                                             message_format = {uint, 0}},
264 |                 body = Body}.
265 | 
266 | %% @doc Create a new settled amqp10 message using the specified delivery tag
267 | %% and body.
268 | -spec new(delivery_tag(), amqp10_body() | binary()) -> amqp10_msg().
269 | new(DeliveryTag, Body) ->
270 |     new(DeliveryTag, Body, false).
271 | 
272 | 
273 | % First 3 octets are the format
274 | % the last 1 octet is the version
275 | % See 2.8.11 in the spec
276 | %% @doc Set the message format.
277 | -spec set_message_format({non_neg_integer(), non_neg_integer()},
278 |                          amqp10_msg()) -> amqp10_msg().
279 | set_message_format({Format, Version}, #amqp10_msg{transfer = T} = Msg) ->
280 |     <> = <>,
281 |     Msg#amqp10_msg{transfer = T#'v1_0.transfer'{message_format =
282 |                                                 {uint, MsgFormat}}}.
283 | 
284 | %% @doc Set the link handle used for the message transfer.
285 | -spec set_handle(non_neg_integer(), amqp10_msg()) -> amqp10_msg().
286 | set_handle(Handle, #amqp10_msg{transfer = T} = Msg) ->
287 |     Msg#amqp10_msg{transfer = T#'v1_0.transfer'{handle = {uint, Handle}}}.
288 | 
289 | %% @doc Set the settledment mode.
290 | %% Settled=true means the message is considered settled
291 | %% as soon as sent and no disposition will be issued by the receiver.
292 | %% Settled=false will delay settlement until a disposition has been received.
293 | %% A disposition will be notified to the sender by a message of the
294 | %% following stucture:
295 | %% {amqp10_disposition, {accepted | rejected, DeliveryTag}}
296 | -spec set_settled(boolean(), amqp10_msg()) -> amqp10_msg().
297 | set_settled(Settled, #amqp10_msg{transfer = T} = Msg) ->
298 |     Msg#amqp10_msg{transfer = T#'v1_0.transfer'{settled = Settled}}.
299 | 
300 | %% @doc Set amqp message headers.
301 | -spec set_headers(#{atom() => any()}, amqp10_msg()) -> amqp10_msg().
302 | set_headers(Headers, #amqp10_msg{header = undefined} = Msg) ->
303 |     set_headers(Headers, Msg#amqp10_msg{header = #'v1_0.header'{}});
304 | set_headers(Headers, #amqp10_msg{header = Current} = Msg) ->
305 |     H = maps:fold(fun(durable, V, Acc) ->
306 |                           Acc#'v1_0.header'{durable = V};
307 |                      (priority, V, Acc) ->
308 |                           Acc#'v1_0.header'{priority = {uint, V}};
309 |                      (first_acquirer, V, Acc) ->
310 |                           Acc#'v1_0.header'{first_acquirer = V};
311 |                      (ttl, V, Acc) ->
312 |                           Acc#'v1_0.header'{ttl = {uint, V}};
313 |                      (delivery_count, V, Acc) ->
314 |                           Acc#'v1_0.header'{delivery_count = {uint, V}}
315 |                   end, Current, Headers),
316 |     Msg#amqp10_msg{header = H}.
317 | 
318 | %% @doc Set amqp message properties.
319 | -spec set_properties(amqp10_properties(), amqp10_msg()) -> amqp10_msg().
320 | set_properties(Props, #amqp10_msg{properties = undefined} = Msg) ->
321 |     set_properties(Props, Msg#amqp10_msg{properties = #'v1_0.properties'{}});
322 | set_properties(Props, #amqp10_msg{properties = Current} = Msg) ->
323 |     % TODO many fields are `any` types and we need to try to type tag them
324 |     P = maps:fold(fun(message_id, V, Acc) when is_binary(V) ->
325 |                           % message_id can be any type but we restrict it here
326 |                           Acc#'v1_0.properties'{message_id = utf8(V)};
327 |                      (user_id, V, Acc) ->
328 |                           Acc#'v1_0.properties'{user_id = utf8(V)};
329 |                      (to, V, Acc) ->
330 |                           Acc#'v1_0.properties'{to = utf8(V)};
331 |                      (subject, V, Acc) ->
332 |                           Acc#'v1_0.properties'{subject = utf8(V)};
333 |                      (reply_to, V, Acc) ->
334 |                           Acc#'v1_0.properties'{reply_to = utf8(V)};
335 |                      (correlation_id, V, Acc) ->
336 |                           Acc#'v1_0.properties'{correlation_id = utf8(V)};
337 |                      (content_type, V, Acc) ->
338 |                           Acc#'v1_0.properties'{content_type = sym(V)};
339 |                      (content_encoding, V, Acc) ->
340 |                           Acc#'v1_0.properties'{content_encoding = sym(V)};
341 |                      (absolute_expiry_time, V, Acc) ->
342 |                           Acc#'v1_0.properties'{absolute_expiry_time = {timestamp, V}};
343 |                      (creation_time, V, Acc) ->
344 |                           Acc#'v1_0.properties'{creation_time = {timestamp, V}};
345 |                      (group_id, V, Acc) ->
346 |                           Acc#'v1_0.properties'{group_id = utf8(V)};
347 |                      (group_sequence, V, Acc) ->
348 |                           Acc#'v1_0.properties'{group_sequence = uint(V)};
349 |                      (reply_to_group_id, V, Acc) ->
350 |                           Acc#'v1_0.properties'{reply_to_group_id = utf8(V)}
351 |                   end, Current, Props),
352 |     Msg#amqp10_msg{properties = P}.
353 | 
354 | -spec set_application_properties(#{binary() | string() => binary() | integer() | string()},
355 |                                  amqp10_msg()) -> amqp10_msg().
356 | set_application_properties(Props,
357 |                            #amqp10_msg{application_properties = undefined} =
358 |                            Msg) ->
359 |     APs = #'v1_0.application_properties'{content = []},
360 |     set_application_properties(Props,
361 |                                Msg#amqp10_msg{application_properties = APs});
362 | set_application_properties(
363 |   Props0, #amqp10_msg{application_properties =
364 |                       #'v1_0.application_properties'{content = APs0}} = Msg) ->
365 |     Props = maps:fold(fun (K, V, S) ->
366 |                               S#{utf8(K) => wrap_ap_value(V)}
367 |                       end, maps:from_list(APs0), Props0),
368 |     APs = #'v1_0.application_properties'{content = maps:to_list(Props)},
369 |     Msg#amqp10_msg{application_properties = APs}.
370 | 
371 | -spec set_delivery_annotations(#{binary() => binary() | integer() | string()},
372 |                                  amqp10_msg()) -> amqp10_msg().
373 | set_delivery_annotations(Props,
374 |                          #amqp10_msg{delivery_annotations = undefined} =
375 |                          Msg) ->
376 |     Anns = #'v1_0.delivery_annotations'{content = []},
377 |     set_delivery_annotations(Props,
378 |                              Msg#amqp10_msg{delivery_annotations = Anns});
379 | set_delivery_annotations(
380 |   Props0, #amqp10_msg{delivery_annotations =
381 |                       #'v1_0.delivery_annotations'{content = Anns0}} = Msg) ->
382 |     Anns = maps:fold(fun (K, V, S) ->
383 |                              S#{sym(K) => wrap_ap_value(V)}
384 |                      end, maps:from_list(Anns0), Props0),
385 |     Anns1 = #'v1_0.delivery_annotations'{content = maps:to_list(Anns)},
386 |     Msg#amqp10_msg{delivery_annotations = Anns1}.
387 | 
388 | -spec set_message_annotations(#{binary() => binary() | integer() | string()},
389 |                                  amqp10_msg()) -> amqp10_msg().
390 | set_message_annotations(Props,
391 |                          #amqp10_msg{message_annotations = undefined} =
392 |                          Msg) ->
393 |     Anns = #'v1_0.message_annotations'{content = []},
394 |     set_message_annotations(Props,
395 |                              Msg#amqp10_msg{message_annotations = Anns});
396 | set_message_annotations(
397 |   Props0, #amqp10_msg{message_annotations =
398 |                       #'v1_0.message_annotations'{content = Anns0}} = Msg) ->
399 |     Anns = maps:fold(fun (K, V, S) ->
400 |                              S#{sym(K) => wrap_ap_value(V)}
401 |                      end, maps:from_list(Anns0), Props0),
402 |     Anns1 = #'v1_0.message_annotations'{content = maps:to_list(Anns)},
403 |     Msg#amqp10_msg{message_annotations = Anns1}.
404 | 
405 | wrap_ap_value(true) ->
406 |     {boolean, true};
407 | wrap_ap_value(false) ->
408 |     {boolean, false};
409 | wrap_ap_value(V) when is_integer(V) ->
410 |     {uint, V};
411 | wrap_ap_value(V) when is_binary(V) ->
412 |     utf8(V);
413 | wrap_ap_value(V) when is_list(V) ->
414 |     utf8(list_to_binary(V));
415 | wrap_ap_value(V) when is_atom(V) ->
416 |     utf8(atom_to_list(V)).
417 | 
418 | 
419 | %% LOCAL
420 | header_value(durable, undefined) -> false;
421 | header_value(priority, undefined) -> 4;
422 | header_value(first_acquirer, undefined) -> false;
423 | header_value(delivery_count, undefined) -> 0;
424 | header_value(Key, {_Type, Value}) -> header_value(Key, Value);
425 | header_value(_Key, Value) -> Value.
426 | 
427 | parse_from_amqp(#'v1_0.header'{} = Header, AmqpMsg) ->
428 |     AmqpMsg#amqp10_msg{header = Header};
429 | parse_from_amqp(#'v1_0.delivery_annotations'{} = DAS, AmqpMsg) ->
430 |     AmqpMsg#amqp10_msg{delivery_annotations = DAS};
431 | parse_from_amqp(#'v1_0.message_annotations'{} = DAS, AmqpMsg) ->
432 |     AmqpMsg#amqp10_msg{message_annotations = DAS};
433 | parse_from_amqp(#'v1_0.properties'{} = Header, AmqpMsg) ->
434 |     AmqpMsg#amqp10_msg{properties = Header};
435 | parse_from_amqp(#'v1_0.application_properties'{} = APs, AmqpMsg) ->
436 |     AmqpMsg#amqp10_msg{application_properties = APs};
437 | parse_from_amqp(#'v1_0.amqp_value'{} = Value, AmqpMsg) ->
438 |     AmqpMsg#amqp10_msg{body = Value};
439 | parse_from_amqp(#'v1_0.amqp_sequence'{} = Seq, AmqpMsg) ->
440 |     AmqpMsg#amqp10_msg{body = [Seq]};
441 | parse_from_amqp(#'v1_0.data'{} = Data, AmqpMsg) ->
442 |     AmqpMsg#amqp10_msg{body = [Data]};
443 | parse_from_amqp(#'v1_0.footer'{} = Header, AmqpMsg) ->
444 |     AmqpMsg#amqp10_msg{footer = Header}.
445 | 
446 | unpack(V) -> amqp10_client_types:unpack(V).
447 | utf8(V) -> amqp10_client_types:utf8(V).
448 | sym(B) when is_list(B) -> {symbol, list_to_binary(B)};
449 | sym(B) when is_binary(B) -> {symbol, B}.
450 | uint(B) -> {uint, B}.
451 | 
452 | has_value(undefined) -> false;
453 | has_value(_) -> true.
454 | 


--------------------------------------------------------------------------------
/test/activemq_ct_helpers.erl:
--------------------------------------------------------------------------------
  1 | %% This Source Code Form is subject to the terms of the Mozilla Public
  2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
  3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
  4 | %%
  5 | %% Copyright (c) 2017-2020 VMware, Inc. or its affiliates.  All rights reserved.
  6 | %%
  7 | 
  8 | -module(activemq_ct_helpers).
  9 | 
 10 | -include_lib("common_test/include/ct.hrl").
 11 | 
 12 | -export([setup_steps/1,
 13 |          teardown_steps/0,
 14 |          ensure_activemq_cmd/1,
 15 |          init_config_filename/2,
 16 |          init_tcp_port_numbers/1,
 17 |          start_activemq_nodes/1,
 18 |          stop_activemq_nodes/1]).
 19 | 
 20 | setup_steps(ConfigFileName) ->
 21 |     [fun ensure_activemq_cmd/1,
 22 |      fun(Config) -> init_config_filename(Config, ConfigFileName) end,
 23 |      fun init_tcp_port_numbers/1,
 24 |      fun start_activemq_nodes/1].
 25 | 
 26 | teardown_steps() ->
 27 |     [fun stop_activemq_nodes/1].
 28 | 
 29 | ensure_activemq_cmd(Config) ->
 30 |     ActivemqCmd= case rabbit_ct_helpers:get_config(Config, activemq_cmd) of
 31 |         undefined ->
 32 |             case os:getenv("ACTIVEMQ") of
 33 |                 false -> "activemq";
 34 |                 M     -> M
 35 |             end;
 36 |         M ->
 37 |             M
 38 |     end,
 39 |     Cmd = [ActivemqCmd, "--version"],
 40 |     case rabbit_ct_helpers:exec(Cmd, [{match_stdout, "ActiveMQ"}]) of
 41 |         {ok, _} -> rabbit_ct_helpers:set_config(Config,
 42 |                                                 {activemq_cmd, ActivemqCmd});
 43 |         _       -> {skip,
 44 |                     "ActiveMQ CLI required, " ++
 45 |                     "please set ACTIVEMQ or 'activemq_cmd' in ct config"}
 46 |     end.
 47 | 
 48 | init_config_filename(Config, FileName) ->
 49 |     ConfigFile = filename:join([?config(data_dir, Config),
 50 |                                 "conf", FileName]),
 51 |     rabbit_ct_helpers:set_config(Config, {activemq_config_filename, ConfigFile}).
 52 | 
 53 | init_tcp_port_numbers(Config) ->
 54 |     TCPPort = 21000,
 55 |     NodeConfig = [{nodename, activemq},
 56 |                   {initial_nodename, activemq},
 57 |                   {tcp_port_amqp, TCPPort}],
 58 |     rabbit_ct_helpers:set_config(Config, {rmq_nodes, [NodeConfig]}).
 59 | 
 60 | start_activemq_nodes(Config) ->
 61 |     Config1 = rabbit_ct_helpers:set_config(Config,
 62 |                                            [{rmq_hostname, "localhost"}]),
 63 |     ActivemqCmd = ?config(activemq_cmd, Config1),
 64 |     TCPPort = rabbit_ct_broker_helpers:get_node_config(Config1, 0, tcp_port_amqp),
 65 |     ConfigFile = ?config(activemq_config_filename, Config1),
 66 |     Cmd = [ActivemqCmd,
 67 |            "start",
 68 |            {"-Dtestsuite.tcp_port_amqp=~b", [TCPPort]},
 69 |            {"xbean:file:~s", [ConfigFile]}],
 70 |     case rabbit_ct_helpers:exec(Cmd, []) of
 71 |         {ok, _} -> wait_for_activemq_nodes(Config1);
 72 |         Error   -> ct:pal("Error: ~p", [Error]),
 73 |                    {skip, "Failed to start ActiveMQ"}
 74 |     end.
 75 | 
 76 | wait_for_activemq_nodes(Config) ->
 77 |     Hostname = ?config(rmq_hostname, Config),
 78 |     Ports = rabbit_ct_broker_helpers:get_node_configs(Config, tcp_port_amqp),
 79 |     wait_for_activemq_ports(Config, Hostname, Ports).
 80 | 
 81 | wait_for_activemq_ports(Config, Hostname, [Port | Rest]) ->
 82 |     ct:pal(?LOW_IMPORTANCE, "Waiting for ActiveMQ on port ~b", [Port]),
 83 |     case wait_for_activemq_port(Hostname, Port, 60) of
 84 |         ok ->
 85 |             ct:pal(?LOW_IMPORTANCE, "ActiveMQ ready on port ~b", [Port]),
 86 |             wait_for_activemq_ports(Config, Hostname, Rest);
 87 |         {error, _} ->
 88 |             Msg = lists:flatten(
 89 |                     io_lib:format(
 90 |                       "Failed to start ActiveMQ on port ~b; see ActiveMQ logs",
 91 |                       [Port])),
 92 |             ct:pal(?LOW_IMPORTANCE, Msg, []),
 93 |             {skip, Msg}
 94 |     end;
 95 | wait_for_activemq_ports(Config, _, []) ->
 96 |     Config.
 97 | 
 98 | wait_for_activemq_port(_, _, 0) ->
 99 |     {error, econnrefused};
100 | wait_for_activemq_port(Hostname, Port, Retries) ->
101 |     case gen_tcp:connect(Hostname, Port, []) of
102 |         {ok, Connection} ->
103 |             gen_tcp:close(Connection),
104 |             ok;
105 |         {error, econnrefused} ->
106 |             timer:sleep(1000),
107 |             wait_for_activemq_port(Hostname, Port, Retries - 1);
108 |         Error ->
109 |             Error
110 |     end.
111 | 
112 | stop_activemq_nodes(Config) ->
113 |     ActivemqCmd = ?config(activemq_cmd, Config),
114 |     Cmd = [ActivemqCmd, "stop"],
115 |     case rabbit_ct_helpers:exec(Cmd, []) of
116 |         {ok, _} -> Config;
117 |         Error   -> ct:pal("Error: ~p", [Error]),
118 |                    {skip, "Failed to stop ActiveMQ"}
119 |     end.
120 | 


--------------------------------------------------------------------------------
/test/mock_server.erl:
--------------------------------------------------------------------------------
 1 | %% This Source Code Form is subject to the terms of the Mozilla Public
 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
 4 | %%
 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates.  All rights reserved.
 6 | %%
 7 | -module(mock_server).
 8 | 
 9 | %% API functions
10 | -export([start/1,
11 |          set_steps/2,
12 |          stop/1,
13 |          run/1,
14 |          amqp_step/1,
15 |          send_amqp_header_step/1,
16 |          recv_amqp_header_step/1
17 |         ]).
18 | 
19 | -include_lib("src/amqp10_client.hrl").
20 | 
21 | start(Port) ->
22 |     {ok, LSock} = gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]),
23 |     Pid = spawn(?MODULE, run, [LSock]),
24 |     {LSock, Pid}.
25 | 
26 | set_steps({_Sock, Pid}, Steps) ->
27 |     Pid ! {set_steps, Steps},
28 |     ok.
29 | 
30 | stop({S, P}) ->
31 |     P ! close,
32 |     gen_tcp:close(S),
33 |     exit(P, stop).
34 | 
35 | run(Listener) ->
36 |     receive
37 |         {set_steps, Steps} ->
38 |             {ok, Sock} = gen_tcp:accept(Listener),
39 |             lists:foreach(fun(S) -> S(Sock) end, Steps),
40 |             receive
41 |                 close -> ok
42 |             end
43 |     end.
44 | 
45 | 
46 | send(Socket, Ch, Records) ->
47 |     Encoded = [amqp10_framing:encode_bin(R) || R <- Records],
48 |     Frame = amqp10_binary_generator:build_frame(Ch, Encoded),
49 |     ok = gen_tcp:send(Socket, Frame).
50 | 
51 | recv(Sock) ->
52 |     {ok, <>} = gen_tcp:recv(Sock, 8),
54 |     {ok, Data} = gen_tcp:recv(Sock, Length - 8),
55 |     {PerfDesc, Payload} = amqp10_binary_parser:parse(Data),
56 |     Perf = amqp10_framing:decode(PerfDesc),
57 |     {Ch, Perf, Payload}.
58 | 
59 | amqp_step(Fun) ->
60 |     fun (Sock) ->
61 |             Recv = recv(Sock),
62 |             ct:pal("AMQP Step receieved ~p~n", [Recv]),
63 |             case Fun(Recv) of
64 |                 {_Ch, []} -> ok;
65 |                 {Ch, {multi, Records}} ->
66 |                     [begin
67 |                          ct:pal("AMQP multi Step send ~p~n", [R]),
68 |                          send(Sock, Ch, R)
69 |                      end || R <- Records];
70 |                 {Ch, Records} ->
71 |                     ct:pal("AMQP Step send ~p~n", [Records]),
72 |                     send(Sock, Ch, Records)
73 |             end
74 |     end.
75 | 
76 | 
77 | send_amqp_header_step(Sock) ->
78 |     ct:pal("Sending AMQP protocol header"),
79 |     ok = gen_tcp:send(Sock, ?AMQP_PROTOCOL_HEADER).
80 | 
81 | recv_amqp_header_step(Sock) ->
82 |     ct:pal("Receiving AMQP protocol header"),
83 |     {ok, R} = gen_tcp:recv(Sock, 8),
84 |     ct:pal("handshake Step receieved ~p~n", [R]).
85 | 


--------------------------------------------------------------------------------
/test/msg_SUITE.erl:
--------------------------------------------------------------------------------
  1 | %% This Source Code Form is subject to the terms of the Mozilla Public
  2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
  3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/.
  4 | %%
  5 | %% Copyright (c) 2017-2020 VMware, Inc. or its affiliates.  All rights reserved.
  6 | %%
  7 | 
  8 | -module(msg_SUITE).
  9 | 
 10 | -include_lib("common_test/include/ct.hrl").
 11 | -include_lib("eunit/include/eunit.hrl").
 12 | 
 13 | -include_lib("amqp10_common/include/amqp10_framing.hrl").
 14 | 
 15 | -compile(export_all).
 16 | 
 17 | 
 18 | all() ->
 19 |     [{group, tests}].
 20 | 
 21 | groups() ->
 22 |     [
 23 |      {tests, [parallel], [
 24 |                           minimal_input,
 25 |                           amqp_bodies,
 26 |                           full_input,
 27 |                           new,
 28 |                           new_with_options,
 29 |                           new_amqp_value,
 30 |                           new_amqp_sequence,
 31 |                           set_message_format,
 32 |                           set_headers,
 33 |                           update_headers,
 34 |                           set_properties,
 35 |                           set_application_properties,
 36 |                           set_delivery_annotations,
 37 |                           set_message_annotations,
 38 |                           to_amqp_records,
 39 |                           set_handle,
 40 |                           body_bin_data,
 41 |                           body_bin_amqp_value,
 42 |                           body_bin_amqp_sequence
 43 | 
 44 |                          ]}
 45 |     ].
 46 | 
 47 | %% -------------------------------------------------------------------
 48 | %% Testsuite setup/teardown.
 49 | %% -------------------------------------------------------------------
 50 | 
 51 | init_per_suite(Config) -> Config.
 52 | 
 53 | end_per_suite(Config) -> Config.
 54 | 
 55 | %% -------------------------------------------------------------------
 56 | %% Groups.
 57 | %% -------------------------------------------------------------------
 58 | 
 59 | init_per_group(_, Config) -> Config.
 60 | 
 61 | end_per_group(_, Config) -> Config.
 62 | 
 63 | minimal_input(_Config) ->
 64 |     Tag = <<"tag">>,
 65 |     Content = <<"content">>,
 66 |     Input = [#'v1_0.transfer'{delivery_tag = {utf8, Tag},
 67 |                               delivery_id = {uint, 5672}},
 68 |              #'v1_0.data'{content = Content}],
 69 |     Res = amqp10_msg:from_amqp_records(Input),
 70 |     Tag = amqp10_msg:delivery_tag(Res),
 71 |     5672 = amqp10_msg:delivery_id(Res),
 72 |     undefined = amqp10_msg:message_format(Res),
 73 |     #{} = amqp10_msg:headers(Res),
 74 |     #{} = amqp10_msg:delivery_annotations(Res),
 75 |     #{} = amqp10_msg:message_annotations(Res),
 76 |     #{} = amqp10_msg:properties(Res),
 77 |     #{} = amqp10_msg:application_properties(Res),
 78 |     false =  amqp10_msg:header(durable, Res),
 79 |     4 = amqp10_msg:header(priority, Res),
 80 |     false = amqp10_msg:header(first_acquirer, Res),
 81 |     0 = amqp10_msg:header(delivery_count, Res),
 82 |     undefined = amqp10_msg:header(ttl, Res).
 83 | 
 84 | amqp_bodies(_Config) ->
 85 |     Tag = <<"tag">>,
 86 |     Content = <<"hi">>,
 87 |     Data = #'v1_0.data'{content = Content},
 88 |     Value = #'v1_0.amqp_value'{content = utf8("hi")},
 89 |     Seq = #'v1_0.amqp_sequence'{content = {list, [utf8("hi"), utf8("there")]}},
 90 |     Transfer = #'v1_0.transfer'{delivery_tag = {utf8, Tag}},
 91 | 
 92 |     Res1 = amqp10_msg:from_amqp_records([Transfer, Data]),
 93 |     [<<"hi">>] = amqp10_msg:body(Res1),
 94 | 
 95 |     Res2 = amqp10_msg:from_amqp_records([Transfer, Value]),
 96 |     #'v1_0.amqp_value'{content = {utf8, <<"hi">>}} = amqp10_msg:body(Res2),
 97 | 
 98 |     Res3 = amqp10_msg:from_amqp_records([Transfer, Seq]),
 99 |     [#'v1_0.amqp_sequence'{content = {list, [{utf8, <<"hi">>},
100 |                                              {utf8, <<"there">>}
101 |                                             ]}}] = amqp10_msg:body(Res3).
102 | 
103 | full_input(_Config) ->
104 |     Tag = <<"tag">>,
105 |     Content = <<"content">>,
106 |     %% Format / Version
107 |     <> = <<101:24/unsigned, 2:8/unsigned>>,
108 |     Input = [#'v1_0.transfer'{delivery_tag = utf8("tag"),
109 |                               message_format = {uint, MessageFormat}
110 |                              },
111 |              #'v1_0.header'{durable = true, priority = 9, ttl = 1004,
112 |                             first_acquirer = true, delivery_count = 101},
113 |              #'v1_0.delivery_annotations'{content =
114 |                                           [{utf8("key"), utf8("value")}
115 |                                           ]},
116 |              #'v1_0.message_annotations'{content =
117 |                                           [{utf8("key"), utf8("value")}
118 |                                           ]},
119 |              #'v1_0.properties'{message_id = utf8("msg-id"),
120 |                                 user_id = utf8("zen"),
121 |                                 to = utf8("to"),
122 |                                 subject = utf8("subject"),
123 |                                 reply_to = utf8("reply-to"),
124 |                                 correlation_id = utf8("correlation_id"),
125 |                                 content_type = {symbol, <<"utf8">>},
126 |                                 content_encoding = {symbol, <<"gzip">>},
127 |                                 absolute_expiry_time = {timestamp, 1000},
128 |                                 creation_time = {timestamp, 10},
129 |                                 group_id = utf8("group-id"),
130 |                                 group_sequence = {uint, 33},
131 |                                 reply_to_group_id = utf8("reply-to-group-id")
132 |                                },
133 |              #'v1_0.application_properties'{content =
134 |                                             [{utf8("key"), utf8("value")}]},
135 |              #'v1_0.data'{content = Content},
136 |              #'v1_0.footer'{content = [{utf8("key"), utf8("value")}]}
137 |             ],
138 |     Res = amqp10_msg:from_amqp_records(Input),
139 |     Tag = amqp10_msg:delivery_tag(Res),
140 |     {101, 2} = amqp10_msg:message_format(Res),
141 |     Headers = amqp10_msg:headers(Res),
142 |     #{durable := true,
143 |       priority := 9,
144 |       first_acquirer := true,
145 |       delivery_count := 101,
146 |       ttl := 1004} = Headers,
147 | 
148 |     % header/2
149 |     true =  amqp10_msg:header(durable, Res),
150 |     9 = amqp10_msg:header(priority, Res),
151 |     true = amqp10_msg:header(first_acquirer, Res),
152 |     101 = amqp10_msg:header(delivery_count, Res),
153 |     1004 = amqp10_msg:header(ttl, Res), % no default
154 | 
155 |     #{<<"key">> := <<"value">>} = amqp10_msg:delivery_annotations(Res),
156 |     #{<<"key">> := <<"value">>} = amqp10_msg:message_annotations(Res),
157 |     #{message_id := <<"msg-id">>,
158 |       user_id := <<"zen">>,
159 |       to := <<"to">>,
160 |       subject := <<"subject">>,
161 |       reply_to := <<"reply-to">>,
162 |       correlation_id := <<"correlation_id">>,
163 |       content_type := <<"utf8">>,
164 |       content_encoding := <<"gzip">>,
165 |       absolute_expiry_time := 1000,
166 |       creation_time := 10,
167 |       group_id := <<"group-id">>,
168 |       group_sequence := 33,
169 |       reply_to_group_id := <<"reply-to-group-id">>} = amqp10_msg:properties(Res),
170 | 
171 |     #{<<"key">> := <<"value">>} = amqp10_msg:application_properties(Res),
172 | 
173 |     ?assertEqual([Content], amqp10_msg:body(Res)),
174 | 
175 |     #{<<"key">> := <<"value">>} = amqp10_msg:footer(Res).
176 | 
177 | new(_Config) ->
178 |     Tag = <<"tag">>,
179 |     Body = <<"hi">>,
180 |     Msg = amqp10_msg:new(Tag, Body),
181 |     Tag = amqp10_msg:delivery_tag(Msg),
182 |     [<<"hi">>] = amqp10_msg:body(Msg).
183 | 
184 | new_with_options(_Config) ->
185 |     Tag = <<"tag">>,
186 |     Body = <<"hi">>,
187 |     Msg = amqp10_msg:new(Tag, Body, true),
188 |     Tag = amqp10_msg:delivery_tag(Msg),
189 |     true = amqp10_msg:settled(Msg).
190 | 
191 | new_amqp_value(_Config) ->
192 |     Tag = <<"tag">>,
193 |     Body = #'v1_0.amqp_value'{content = {utf8, <<"hi">>}},
194 |     Msg = amqp10_msg:new(Tag, Body),
195 |     Tag = amqp10_msg:delivery_tag(Msg),
196 |     Body = amqp10_msg:body(Msg).
197 | 
198 | new_amqp_sequence(_Config) ->
199 |     Tag = <<"tag">>,
200 |     Body = #'v1_0.amqp_sequence'{content = {list, [utf8("hi"), utf8("there")]}},
201 |     Msg = amqp10_msg:new(Tag, [Body]),
202 |     Tag = amqp10_msg:delivery_tag(Msg),
203 |     [Body] = amqp10_msg:body(Msg).
204 | 
205 | set_message_format(_Config) ->
206 |     MsgFormat = {103, 3},
207 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
208 |     Msg1 = amqp10_msg:set_message_format(MsgFormat, Msg0),
209 |     MsgFormat = amqp10_msg:message_format(Msg1).
210 | 
211 | set_headers(_Config) ->
212 |     Headers = #{durable => true},
213 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
214 |     Msg1 = amqp10_msg:set_headers(Headers, Msg0),
215 |     #{durable := true} = amqp10_msg:headers(Msg1).
216 | 
217 | update_headers(_Config) ->
218 |     Headers = #{priority => 5},
219 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
220 |     Msg1 = amqp10_msg:set_headers(Headers, Msg0),
221 |     #{priority := 5} = amqp10_msg:headers(Msg1),
222 |     Msg2 = amqp10_msg:set_headers(Headers#{priority => 9}, Msg1),
223 |     #{priority := 9} = amqp10_msg:headers(Msg2).
224 | 
225 | set_handle(_Config) ->
226 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
227 |     Msg1 = amqp10_msg:set_handle(42, Msg0),
228 |     42 = amqp10_msg:handle(Msg1).
229 | 
230 | set_properties(_Config) ->
231 |     Props = #{message_id => <<"msg-id">>,
232 |               user_id => <<"zen">>,
233 |               to => <<"to">>,
234 |               subject => <<"subject">>,
235 |               reply_to => <<"reply-to">>,
236 |               correlation_id => <<"correlation_id">>,
237 |               content_type => <<"utf8">>,
238 |               content_encoding => <<"gzip">>,
239 |               absolute_expiry_time => 1000,
240 |               creation_time => 10,
241 |               group_id => <<"group-id">>,
242 |               group_sequence => 33,
243 |               reply_to_group_id => <<"reply-to-group-id">>},
244 | 
245 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
246 |     Msg1 = amqp10_msg:set_properties(Props, Msg0),
247 |     Props = amqp10_msg:properties(Msg1).
248 | 
249 | set_application_properties(_Config) ->
250 |     Props = #{"key" => "value",
251 |               <<"key2">> => <<"value2">>},
252 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
253 |     Msg1 = amqp10_msg:set_application_properties(Props, Msg0),
254 |     #{<<"key">> := <<"value">>,
255 |       <<"key2">> := <<"value2">>} = amqp10_msg:application_properties(Msg1),
256 |     ok.
257 | 
258 | set_delivery_annotations(_Config) ->
259 |     Props = #{<<"x-key">> => "value",
260 |               <<"x-key2">> => 9},
261 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
262 |     Msg1 = amqp10_msg:set_delivery_annotations(Props, Msg0),
263 |     #{<<"x-key">> := <<"value">>,
264 |       <<"x-key2">> := 9} = amqp10_msg:delivery_annotations(Msg1).
265 | 
266 | set_message_annotations(_Config) ->
267 |     Props = #{<<"x-key">> => "value",
268 |               <<"x-key2">> => 9},
269 |     Msg0 = amqp10_msg:new(<<"tag">>,  <<"hi">>),
270 |     Msg1 = amqp10_msg:set_message_annotations(Props, Msg0),
271 |     #{<<"x-key">> := <<"value">>,
272 |       <<"x-key2">> := 9} = amqp10_msg:message_annotations(Msg1).
273 | 
274 | to_amqp_records(_Config) ->
275 |     Msg0 = amqp10_msg:new(<<"tag">>, <<"data">>),
276 |     Msg = amqp10_msg:set_headers(#{durable => true}, Msg0),
277 |     [#'v1_0.transfer'{},
278 |      #'v1_0.header'{durable = true},
279 |      #'v1_0.data'{content = <<"data">>}] =
280 |      amqp10_msg:to_amqp_records(Msg).
281 | 
282 | body_bin_data(_) ->
283 |     Body = [
284 |             #'v1_0.data'{content = <<"one">>},
285 |             #'v1_0.data'{content = <<"two">>}
286 |            ],
287 |     Msg = amqp10_msg:new(<<55>>, Body),
288 |     Bin = amqp10_msg:body_bin(Msg),
289 |     Body = amqp10_framing:decode_bin(Bin),
290 |     ok.
291 | 
292 | body_bin_amqp_value(_) ->
293 |     Body = #'v1_0.amqp_value'{content = {utf8, <<"one">>}},
294 |     Msg = amqp10_msg:new(<<55>>, Body),
295 |     Bin = amqp10_msg:body_bin(Msg),
296 |     [Body] = amqp10_framing:decode_bin(Bin),
297 |     ok.
298 | 
299 | body_bin_amqp_sequence(_) ->
300 |     Body = [
301 |             #'v1_0.amqp_sequence'{content = [{utf8, <<"one">>},
302 |                                              {utf8, <<"blah">>}]},
303 |             #'v1_0.amqp_sequence'{content = [{utf8, <<"two">>}]}
304 |            ],
305 |     Msg = amqp10_msg:new(<<55>>, Body),
306 |     Bin = amqp10_msg:body_bin(Msg),
307 |     Body = amqp10_framing:decode_bin(Bin),
308 |     ok.
309 | 
310 | %% -------------------------------------------------------------------
311 | %% Utilities
312 | %% -------------------------------------------------------------------
313 | 
314 | utf8(S) -> amqp10_client_types:utf8(S).
315 | 


--------------------------------------------------------------------------------
/test/system_SUITE_data/conf/activemq.xml:
--------------------------------------------------------------------------------
  1 | 
 17 | 
 18 | 
 23 | 
 24 |     
 25 |     
 26 |         
 27 |             file:${activemq.conf}/credentials.properties
 28 |         
 29 |     
 30 | 
 31 |    
 32 |     
 35 |     
 36 | 
 37 |     
 40 |     
 41 | 
 42 |         
 43 |             
 44 |               
 45 |                 
 46 |                     
 54 |                   
 55 |                     
 56 |                   
 57 |                 
 58 |               
 59 |             
 60 |         
 61 | 
 62 | 
 63 |         
 70 |         
 71 |             
 72 |         
 73 | 
 74 |         
 81 |         
 82 |             
 83 |         
 84 | 
 85 | 
 86 |           
 91 |           
 92 |             
 93 |                 
 94 |                     
 95 |                 
 96 |                 
 97 |                     
 98 |                 
 99 |                 
100 |                     
101 |                 
102 |             
103 |         
104 | 
105 |         
111 |         
112 |             
113 | 	    
114 |         
115 | 
116 |         
117 |         
118 |             
119 |         
120 | 
121 | 	
122 |         
123 |             
124 |               
126 |               
128 |               
129 |             
130 |           
131 |         
132 |     
133 | 
134 | 
135 | 
136 | 


--------------------------------------------------------------------------------
/test/system_SUITE_data/conf/activemq_no_anon.xml:
--------------------------------------------------------------------------------
  1 | 
 17 | 
 18 | 
 23 | 
 24 |     
 25 |     
 26 |         
 27 |             file:${activemq.conf}/credentials.properties
 28 |         
 29 |     
 30 | 
 31 |    
 32 |     
 35 |     
 36 | 
 37 |     
 40 |     
 41 | 
 42 |         
 43 |             
 44 |               
 45 |                 
 46 |                     
 54 |                   
 55 |                     
 56 |                   
 57 |                 
 58 |               
 59 |             
 60 |         
 61 | 
 62 | 
 63 |         
 70 |         
 71 |             
 72 |         
 73 | 
 74 |         
 81 |         
 82 |             
 83 |         
 84 | 
 85 | 
 86 |           
 91 |           
 92 |             
 93 |                 
 94 |                     
 95 |                 
 96 |                 
 97 |                     
 98 |                 
 99 |                 
100 |                     
101 |                 
102 |             
103 |         
104 | 
105 |         
111 |         
112 |             
113 | 	    
114 |         
115 | 
116 |         
117 |         
118 |             
119 |         
120 | 
121 | 	
122 |           
123 |             
124 |               
126 |               
128 |               
129 |             
130 |           
131 |         
132 |     
133 | 
134 | 
135 | 
136 | 


--------------------------------------------------------------------------------